Leaderboard
Popular Content
Showing content with the highest reputation since 03/24/2025 in all areas
-
We are thrilled to inform you that we have successfully created a backup copy of all ZenHax data! Posted here on reshax, it includes: - 70590 posts in 13300 topics - 480 bms scripts - 1700 tools There are other backups, but they have disadvantages: - zenhax.com backup is slow and lacks attachments. - archive.org copy includes attachments, but its even slower. Both of them are hard to search and navigate. Now this backup has attachments, its quick and you can easily search it. Stay tuned for more updates! 🎉13 points
-
I'm still looking into the bones, trying to get them working correctly. The ones that don't work correctly yet are the models with over 256 bones, such as the one below. The vertex data only uses bytes for the indices, so it has to remap the bones into smaller groups of 0-255. They could have just used Shorts for the indices and it would have been no problem. Had a bit of success with some bones being remapped correctly, but others still not. It seems like some bones maybe aren't needed, or are used for something else. This is pl036 with some bones being moved correctly.13 points
-
10 points
-
From further investigation, it seems that some meshes use up to 8 bone weights/indices, where others only use 4. It also looks like they're split into 2 groups of 4 in the vertex data - i.e 4 weights/4 indices/2nd set of 4 weights/2nd set of 4 indices. So I'll need to redo the way bones are processed to see if that fixes it.9 points
-
9 points
-
There was A LOT of discussion here that was not related to AC Shadows localization. As a moderator I couldn't allow that, so I've removed all off-topic posts. From now on please post only comments that are directly connected with translation work. Thank you. 🙂8 points
-
Version 1.0.0
150 downloads
My old tool for TLOU2 PS4 fixed to work with PC files. Unpack .psarc files with UnPSARC_v2.7, and then you can use any .PAK file with the tool. Use command line, or just drop .PAK file onto the .exe Work same as old tool, described here: https://web.archive.org/web/20230819184855/https://forum.xentax.com/viewtopic.php?t=22580 After extraction, model and skeleton are in separate files. So they must be manually combined to work together. SMD model & skeleton can be just imported separately and then connected in blender. For ascii it will not work, so you have to open model in text editor, remove first line (0), which is bone number, and copy-paste skeleton there instead of that zero. So far tested on models, textures, maps - all looks fine. Animations are probably wrong, i can look into that later.6 points -
Okay, here's a little bit of an update. I've added support for all the vertex types, and the textures should all load correctly now. I've also included the extra UVs where available, although they're not presently used. The bones are still messed up in a lot of cases, so that still needs some work. The materials also need more work. I've disabled the vertex colours for now as that was messing up a lot of the meshes as well. bleach_rebirth_tmd2.zip6 points
-
I'm going to update my PS4 tool for this game to support PC remaster. Maybe it will only be some small change.5 points
-
I made an importer that import every tmd2 model correctly. it can read models with more than 255 bones, all uv layers, all vertex color layers, tangents, binormals, both sets of normals, etc... https://www.nexusmods.com/bleachrebirthofsouls/mods/634 points
-
The kamzik123 texted me and I spoke with him and it seems that my view of him was wrong and there was a misunderstanding. He is currently not planning to release his tool until EOL for good reasons. If you wanna translate this game you can get the texts from attached file and send me the translated version to get a forge patch. (still you must prove that you are a creator by sending the link of your old translations) acs_texts.zip4 points
-
Yes, but it's still in progress, not ready to upload it yet while I iron out a few issues.4 points
-
Another update. Should load almost everything now. Since the last updated, I've integrated reading of the textures and created the materials for each submesh, so they should display correctly. No need to decompress any files first, just load a tmd2 file and it will load the matching texture file. There are issues with some hair meshes - some of them don't seem to have proper textures, maybe they're handled differently, not sure. There are still some texture types that I'm not sure about - they could be specular, metallic, or whatever. As always, it's still a work in progress. bleach_rebirth_tmd2.zip3 points
-
3 points
-
I'm working on a tool to modify existing models or import custom models into Horizon Forbidden West. So far my first test is to fix Aloy's face. I tried a few options, and now I'm sure it will work and looks good, and probably a few mods will be already released soon. Then after import tool is ready, it will be release here on reshax. Here are some example screens. First i reduced her cheeks to 70%, to be sure i can see difference. Since only face shape is changed, you can see the old "fat" shape of peachfuzz (which is not scaled down yet). And then i did 90%2 points
-
Mod released: https://www.nexusmods.com/horizonforbiddenwest/mods/1132 points
-
This script should be better handled import struct import zlib import sys from pathlib import Path def parse_index(input_path): index_data = [] current_path = [] with open(input_path, 'rb') as f: f.seek(44) while True: pos = f.tell() entry_type = struct.unpack('<H', f.read(2))[0] name_length = struct.unpack('<H', f.read(2))[0] if entry_type == 1: f.read(24) folder_name = f.read(name_length).decode('latin-1').rstrip('\x00') current_path = [folder_name] elif entry_type == 0: f.read(8) file_size = struct.unpack('<I', f.read(4))[0] uncompressed_size = struct.unpack('<I', f.read(4))[0] f.read(4) offset = struct.unpack('<I', f.read(4))[0] filename = f.read(name_length).decode('latin-1').rstrip('\x00') full_path = '/'.join(current_path + [filename]) index_data.append({ 'path': full_path, 'offset': offset, 'size': file_size, 'uncompressed_size': uncompressed_size }) else: break if f.tell() >= os.path.getsize(input_path): break return index_data def extract_files(input_path, output_dir, index_data): header_patterns = { b'\x04\x00\x00\x00': 8, b'\x08\x00\x00\x00': 12, b'\x0C\x00\x00\x00': 16, b'\x10\x00\x00\x00': 20, b'\x14\x00\x00\x00': 24, b'\x24\x00\x00\x00': 40, b'\x34\x00\x00\x00': 56 } with open(input_path, 'rb') as f: for entry in index_data: print(f"{entry['offset']} {entry['size']} {entry['path']}") output_path = Path(output_dir) / entry['path'] output_path.parent.mkdir(parents=True, exist_ok=True) f.seek(entry['offset']) full_data = f.read(entry['size']) skip_bytes = 0 for pattern, length in header_patterns.items(): if full_data.startswith(pattern): skip_bytes = length break compressed_data = full_data[skip_bytes:] try: decompressed = zlib.decompress(compressed_data) with open(output_path, 'wb') as out: out.write(decompressed) except Exception: pass if __name__ == '__main__': if len(sys.argv) != 3: print("Usage: python jrz.py <input_file> <output_dir>") sys.exit(1) input_file = sys.argv[1] output_dir = sys.argv[2] import os index_data = parse_index(input_file) extract_files(input_file, output_dir, index_data) jrz.py2 points
-
You don't need to keep asking. If and when there is one, it'll be posted here somewhere.2 points
-
You cen get the same result (and much more!) with ImageHeat https://github.com/bartlomiejduda/ImageHeat2 points
-
This is an introduction section of the forum. Not a good place for posting any samples. If there is a research attempt, then OP should move with this to proper subforum.2 points
-
2 points
-
2 points
-
MySims - Str Tool [PC / Nintendo Wii] By OAleex Test on Nintendo Wii MySims Str Tool.zip2 points
-
yes, everything is almost same. Models, weights, UVs. Expect the fix soon.2 points
-
yes, i downloaded the game, and format is very close. My PS4 tool even exports skeletons, textures and materials without any changes. Must be some small difference only. Textures are not swizzled of course.2 points
-
I'm still tinkering with it as and when I get time, so it's going a bit slow at the moment. The bones are still messed up on a lot of models, and still quite a few texture types that need investigating as to how to use them properly. I think I've sorted all of the different vertex types at least, so the basic models should at least load correctly. I'm not sure about the stages as I don't have any samples of those.2 points
-
They looks like encrypted by the same key. Unity bundles have large, mostly static header and this very helpful in our case: So, if simple XOR is used for encryption, we can try to get key using marked ^ part of header, adapted for Unity version used in game: (not sure in highlighted byte and too lazy to check, but it's better to note and change, if errors occurs) XOR have some interesting features: If you XOR 00 byte with any byte, you get this byte value in place of 00 - so, short keys can be visible with bare eyes if encrypted file have a lots of 00-bytes. If you XOR encrypted file with original one - you'll get <key><key><key> sequence on whole file content. We don't have original file, but we recreated first 35 bytes of it. So, i renamed a copy of game bundle to "s1" and XOR it as new "s1x" file using reconstructed header bytes as key, with XOR tool from Luigi Auriemma: xor s1 s1x 0x556E69747946530000000008352E782E7800323032312E332E39663100000000000000 And got partial <key><key> sequence: So, XOR key is: EA7B2B092B59433996F0B470B7B3FEA7BC0AA57B0208A7314420 Decoding test bundle as "s1t" file with this key: xor s1 s1t 0xEA7B2B092B59433996F0B470B7B3FEA7BC0AA57B0208A7314420 Looks very good ) Drag-and-drop "s1t" file to AssetStudioModGUI, and it loads without issues, so key is valid: Dealing with 36K files in cache isn't easy task, so i done a python script for mass decrypt and save to separate folder: # Mobile Suit Gundam - Iron-Blooded Orphans G bundles unxor import os xor_magic = b"\xBF\x15\x42\x7D" # xor key: EA7B2B092B59433996F0B470B7B3FEA7BC0AA57B0208A7314420 xor_key = bytearray(b"\xea\x7b\x2b\x09\x2b\x59\x43\x39\x96\xf0\xb4\x70\xb7\xb3\xfe\xa7\xbc\x0a\xa5\x7b\x02\x08\xa7\x31\x44\x20") output_path = R"C:\tmp\Cache" with os.scandir('.') as file_paths: for file_path in file_paths: if file_path.is_dir(follow_symlinks=False): pass else: with open(file_path, "rb") as work_file: file_magic = work_file.read(4) if file_magic == xor_magic: print("Processing file:", file_path.name) work_file.seek(0) encrypted_content = work_file.read() encrypted_content_size = len(encrypted_content) decrypted_content = bytearray(encrypted_content_size) xor_key_multiplier = int(encrypted_content_size / 26) + 1 xor_key_array = xor_key * xor_key_multiplier for byte in range(encrypted_content_size): decrypted_content[byte] = encrypted_content[byte] ^ xor_key_array[byte] out_file_name = str(file_path.name) + ".unity3d" write_path = output_path + "\\" + out_file_name os.makedirs(os.path.dirname(write_path), exist_ok=True) open(write_path, "wb").write(decrypted_content) print("Decrypted file saved:", write_path) work_file.close() Must be run inside of "Cache\assets\asset\" folder, and output to temporary folder on disk C (it's SSD usualy, to speed up things). But there's other problem occur - that cache folder taken from a game which survived many updates, so a lots of older copies of same file present here, they have same name in first part (before "_") and version code as second name part. So, i done another python script to separate latest file versions to another folder: # Mobile Suit Gundam - Iron-Blooded Orphans G cache sort import os import shutil work_dictionary = {} output_path = R"C:\tmp\Cache\Assets" with os.scandir('.') as file_paths: for file_path in file_paths: if file_path.is_dir(follow_symlinks=False): pass elif len(os.path.splitext(file_path)[0]) - 2 != 52: pass else: bundle_name = file_path.name[0:33] bundle_version = int(file_path.name[34:52]) if bundle_name in work_dictionary: if work_dictionary[bundle_name] >= bundle_version: pass else: work_dictionary[bundle_name] = bundle_version else: work_dictionary[bundle_name] = bundle_version for item in work_dictionary: file_name = item + "_" + str(work_dictionary[item]) + ".unity3d" destination = output_path + "\\" + file_name os.makedirs(os.path.dirname(destination), exist_ok=True) if os.path.isfile(file_name) == True: shutil.move(file_name, destination) print("Latest version file moved to:", destination) Must be run in folder with decoded bundles, it moves latest version files to Assets subfolder. Then you can move this folder to main game folder "G_2.0.2_apkcombo\assets\bin\Data\" and load game folder to AssetStudioModGUI (took 16+ Gb of RAM): Model export works fine, with animations: Files with names starting with "r" is not encrypted or encrypted using more secure algorithms - no luck here. Mobile_Suit_Gundam_cache_scripts.7z2 points
-
2 points
-
Hey, im just an old game modder/researcher. Cant promise I will be all that active, but its nice to be here2 points
-
Well I updated my tool for ac shadows but as always (like other games) I will keep it private. Why? Because I don't want idiots to take my tool and sell their garbage translations without even leaving a little credit for me. If you need help, you can send me link of your other translations in pm (so I can make sure) and I will help you (I will send the text + put it back into game) to make a patch ( obviously I don't ask for money)2 points
-
Sure if you know full struct. Anyway here's bms for unpacking individual files. I have covered 4 file formats so far... ################################### get BaseFileName basename endian big get Unknown_0_Offset uint32 get Unknown_CRC uint32 idstring BANK getdstring Dummy 0x0C get TotalFileSize uint32 get Unknown_0 uint32 get Unknown_1 uint32 get Unknown_2 uint32 get ResourceCount uint32 get Unknown_3 uint32 get ResourceTableOffset uint32 get Unknown_1_Offset uint32 get Unknown_2_Offset uint32 get Unknown_3_Offset uint32 goto ResourceTableOffset get ResourceTableSize uint32 get Unknown_CRC uint32 for i = 0 < ResourceCount get ResourceOffset uint32 get ResourceSize uint32 get ResourceType uint32 get ResourceNameCRC uint32 savepos cPos goto ResourceOffset getdstring Dummy 0xC getdstring Sign 0x3 if Sign == "DD3" set Ext string "3dd" # Hiearchy elif Sign == "GD3" set Ext string "3dg" # Geometry elif Sign == "MD2" set Ext string "2dm" # Material elif Sign == "BD2" set Ext string "2db" # Texture else set Ext string "dat" # ? endif goto cPos string FileName p= "%s/0x%08x.%s" BaseFileName ResourceOffset Ext log FileName ResourceOffset ResourceSize next i And here Noesis for textures... from inc_noesis import * import noesis import rapi import os def registerNoesisTypes(): handle = noesis.register("Test Drive Unlimited - Xbox 360 Texture", ".2db") noesis.setHandlerTypeCheck(handle, noepyCheckType) noesis.setHandlerLoadRGBA(handle, noepyLoadRGBA) noesis.logPopup() return 1 def noepyCheckType(data): bs = NoeBitStream(data,NOE_BIGENDIAN) if len(data) < 20: return 0 return 1 def noepyLoadRGBA(data, texList): bs = NoeBitStream(data,NOE_BIGENDIAN) BaseName = rapi.getExtensionlessName(rapi.getLocalFileName(rapi.getInputName())) bs.readBytes(40) Width = bs.readUShort() Height = bs.readUShort() Unknown_0 = bs.readUByte() Unknown_1 = bs.readUByte() Unknown_2 = bs.readUByte() Unknown_3 = bs.readUByte() PixelFormat = bs.readUInt() Unknown_5 = bs.readUInt() Unknown_6 = bs.readUInt() Unknown_7 = bs.readUInt() bs.readBytes(4032) if PixelFormat == 132: bs.texSize = Width * Height //2 print("Pixel Format > DXT1") elif PixelFormat == 136: bs.texSize = Width * Height print("Pixel Format > DXT5") elif PixelFormat == 144: bs.texSize = Width * Height * 4 print("Pixel Format > RGBA32") data = bs.readBytes(bs.texSize) if PixelFormat == 132: data = rapi.imageUntile360DXT(rapi.swapEndianArray(data, 2), Width, Height, 8) texFmt = noesis.NOESISTEX_DXT1 elif PixelFormat == 136: data = rapi.imageUntile360DXT(rapi.swapEndianArray(data, 2), Width, Height, 16) texFmt = noesis.NOESISTEX_DXT5 elif PixelFormat == 144: data = rapi.imageUntile360Raw(data, Width, Height, 4) texFmt = noesis.NOESISTEX_RGBA32 texList.append(NoeTexture(rapi.getInputName(), Width, Height, data, texFmt)) return 1 Try to post one sample from x360 and another from pc, files must be same. I mean file name... EDiT: Has Noesis UInt64 data type? Thanks!2 points
-
Hello, everyone. I am Pandora, a Guitar Hero and Rock Band here. Nice to meet you all. As of March 2025, I just noticed that Xentax/Zenhax were deprecated. It is sad that such a tragedy happened to the mod community. I actually was trying to look for help regarding reverse Engineering Guitar Hero 3 for PC, when I knew catched up with the topic. If its allowed, I share some of my links. Please, feel free to have a look. DeviantArt Thanks to the gaming mod community, I was able to extract and share these Guitar Hero 3D models from the PC Version of the game. I am specially proud of Midori and the Male Singer. https://www.deviantart.com/lindsaypandora/art/Midori-3D-Model-Guitar-Hero-3-673967952 https://www.deviantart.com/lindsaypandora/art/Vocalist-3D-Model-Guitar-Hero-3-676369139 YouTube Again, thanks to the Rock Band Modding Community called MiloHax and Emulation Community, I was able to share Performance Mode Videos of Rock Band 2 Deluxe (Unlisted at the time of this message), as well as Guitar Hero Background Videos. My most popular playlist is the PC Version of Guitar Hero 3 https://www.youtube.com/@pandoraday/playlists Again, nice to meet you everyone! Thanks!2 points
-
2 points
-
Here you can use the script that I wrote for the WAD files here: ## Fear Effect 3 Inferno (PS2) (Prototype) - WAD extraction script by BloodRaynare ## For use with QuickBMS get HEADER_SZ long get DIRS long # value is always 1 get DIRNAME string padding 0x88 get ENTRY_OFFSET long # probably isn't needed since the script will always go to this offset after ENTRY_NUMS value is being read anyway get ENTRY_NUMS long for i = 0 < ENTRY_NUMS get FILES_ENTRY_OFFSET long get FILES_ENTRY long get FILES_DATA_SZ long get UNK long # something to do with file types? savepos ENTRY_OFFSET goto FILES_ENTRY_OFFSET for j = 0 < FILES_ENTRY savepos TMP get FNAME string string NAME p "%s/%s" DIRNAME FNAME padding 0x80 0 TMP get OFFSET long get UNK2 long # file type arrays? get SIZE long get ZERO long log NAME OFFSET SIZE next j goto ENTRY_OFFSET next i The models can be imported on blender w/ DragonFF addon as for the TXDs it can be opened with Magic.TXD No clue about animations (ANM) or the other files (DBX, CCD).2 points
-
I finally managed to get rid of a problem where a wronlgy determined face index count led to corrupted obj files. I didn't find a simple solution with Make_H2O_pub, so I decided to fix that bug using hex2obj (which required a special TDUnlimited version to be created). So if you don't want to trick around with a dozen *.objx files (from old hex2obj versions) it's important to use the updated exe (TDUnl) from here. Said exe will create *_corr.obj (instead of *.objx) which means that they can be imported without renaming. (Heaven will tell whether _corr(ection) is correct, always.) The process is like so (read How-to-use_TDUnl_bnk.txt from zip) : Make_H2O exe creates H2O files from bnk files hex2obj creates obj files from bnk using said H2O files bunch of obj files to be imported into blender using included .py file Found sub meshes might be uncomplete - tested three (+ 2 interior) bnk files only Have fun. edit: forgot to mention that doors, steering wheel etc need a positional correction. (The offsets are probably in the bnk files but I'll leave this to you.) And yeah, uvs are missing - progress: 50% for interior (see exe in ...F2.zip) (For chassis uvs need a recheck.) edit: the above mentioned hex2obj version was optimized for creating multiple H2O files (File\SaveAs Mmesh) from TDunlimited bnk files. The caveat is that handling single H2O files is restricted. So don't press the go1 button here. Just the "mesh" button (and maybe correct the vertex count if you're told to do so). Make_H2O_TDU_bnk.zip F2 - exe only - you need the files from the previous zip! Make_H2O-TDUnl-bnk-F2.zip2 points
-
2 points
-
So I've been following this thread from today, seeing all the progress as it comes. My main aim from the beginning was to find Tier's files and edit her to the authentic look. I've been able to find an alternate, albeit possibly outdated now, method of revealing the full 3d models for the files in Noesis. The python plugin is a slight change to DKDave's code that I found earlier in the thread (same file name). It forcibly commits all triangle data to a single mesh, essentially forcing all vertices and submeshes to appear. Maybe someone can benefit from this potential checkpoint? Can't wait to see what modding brings for this amazing game. I'll continue trying to fiddle with the textures tomorrow! Biggest thanks to DKDave again, doing the Soul King's good work. bleach_rebirth_tmd2.py2 points
-
Tool updated. I planned to also improve materials, but had no time. So only this fix.2 points
-
Version 1.0.0
902 downloads
Uploading all my tools will take a lot of time, so this is temporary solution. Most of my tools were released on zenhax, and now available with wayback machine. But how to find them? Here is a list of all topics from Zenhax archived by wayback machine, including attachments and inline scripts. Find topic name with search or filter (see "horizon zero" screenshot as example), then copy URL for the list, and open it. This way you can download almost all of my tools. And also all tools/scripts published by others through the years.2 points -
In the Bleach folder, it's under "00High/Model/chara/" Hope that helps! Every model I've checked so far, including pl002, pl026, pl027, and pl036 have worked great! Thanks DKDave, you're the best.1 point
-
I really like the site! nice work and its always a nice place for a good community, something i would like to know if its possible that the notifications become browser notifications too? on other forums that run under xenforo this is possible and in my case i really like that function because it does keep me updated if someone ever answer me on some thread or if i wanna keep track on a thread, that way also keep me more active in the forum 🙂 EDIT: I just saw the "Enable push notifications" when i opened the notifications, not sure if it was always there or is new, but thanks!1 point
-
1 point
-
Sorry to tell you that but i am not good enough in that. You are on your own. Maybe someone else can help. Anyway you must deal with morton algo.1 point
-
Thanks for reporting! Here's a quick fix (F1) tested with the 350Z Roadster interior bnk only (didn't check the other problem bnks and not the working ones, so keep the first released exe). Surprisingly I found the correct uv address here, for sub mesh 3: edit: shxt, the formula was to simple to apply to the uv addresses of all sub meshes. Only about 50% are correct for 350Z_Rst_I(nterior).bnk, see ...F2.exe in zip here Corrections for chassis uvs are a little bit hard to achieve. Use the updated hex2obj exe (for TDUnlimited) from here. Example for 350Z_Rst.bnk_0042.h2o, that's sub mesh 42 of the 350Z_Rst (uv correction not required any more with Fix3): Correction for sub mesh 39 of the 350Z_Rst steering wheel (uv correction not required any more with Fix3):1 point
-
Hey thanks! this works! The only problem however is that the make H20 cannot read some bnk files like for an example: interior for the 350Z Roadster, both files for the Dino, and the interior of the A4 DTM car. As for the rims it only managed to export the rims from the Lamborghini Diablo GT correctly however for the rest when i import in blender i get a small black dot? (its on the upper right) As for the textures they can be dumped with a ripper I've posted examples for the B. Engineering Edonis below. One folder is from the PC version of the game whereas the other one is from 360. Edonis.rar1 point
-
the mod loader works fine, i tried reinstalling the game and then it worked. Thanks mate1 point
-
Just a small update to add a few more mesh types from the additional samples provided. bleach_rebirth_tmd2.zip1 point
-
Okay, but you need to know its position, which is obtained from QuickBMS1 point
-
I have a small collection of old BMS scripts and a Noesis plugin I just wanted to drop here to share and preserve. These were mainly created to convert texture formats and other files from visual novels such as Cupid Parasite, Taisho x Alice, etc. using the formats CL3, TID, PAK, etc. Noesis Python plugin for CL3 TID Noesis python plugin - tex_IdeaFactory_cl3_tid_BABA.zip BMS script for Taisho x Alice PAK Taisho x Alice.zip BMS script for Hakuouki Reimeiroku Nagorigusa *.mp_ hakuouki_mp.zip BMS script for Cupid Parasite RTEX cupipara.zip BMS script for MEMFS and MEMBODY memfs_membody.zip BMS script for BFSTM bfstm.zip1 point
-
(Little preamble:) Unsure if I should post each archive format separately... I'll start with this one, as I have it best described atm. I reversed bunch of other audio-related formats of Glacier 1 games so I plan to slowly put them all here. Take this as an appetizer 😛 Streams files of Glacier 1 games can be read on their own, they contains all of the required data. It should actually be read before any scenes when someone wants to do anything audio-related to have best support, unlike older Glacier 1 games which had streams.wav. Data in file can be separated into following sections, some have clear indices some can be implicitly inferred: - header - block of WAV data (also contains LIP-encoded segments in some data, current exact structure of these is unknown...) - block of WAV headers (different format than headers in *.WHD files, is much simpler and more concise) - file name table (file names match those in *.WHD and *.SND files, there are some extras though contained within this file so this is not full subset!) - records table (start marked in header along with records count) Block of WAV data seems to be aligned on 0x100 boundary (which coincidentally seems to also be size of header and offset to block of WAV data...). Rest of the file does not seem to have any specific alignment. Any WAV data may be encoded in LIP segments which have variable length. Header of the LIP chunks seems to have size of 0xF00 or 0x1000 (with first header containing 'LIP ' magic). Each record seems to contain a field which can be checked to see if data contains LIP segments or not without the need to rely on comparing magic of each data block. For distance-based records, you will have to look into master record to see if LIP encoding is used. There may be multiple LIP segments in the data block, but only first one has magic in first four bytes. Due to variable data length, we have to find out the right size of the LIP segment first before parsing. It seems to appear roughly every ~4 seconds, but naive formula of `average byte rate * 4` just roughly yields what the LIP segment is. Therefore, there is some guessing work that has to be done on the algorithm side to extract data properly. If anyone could help with reversing these LIP segments, it would be great! They do not seem to correspond to speech necessarily. Current detection method for LIP segments relies on the fact that archive is aligned, we know roughly where the offset should be and that we can calculate exact size of each data block (next block offset - current block offset). There is also additional observation to be made that nearly all LIP segments seem to have around half of their data filled with zeroes. We can also notice that when we subtract real data size, aligned on 0x100 boundary, from whole data block size, we get amount of bytes belonging to LIP segments. We can then calculate from this size amount of LIP segments in the data block. There may be only one such segment (calculated size of all LIP segments is <= 0x1000), which does not require us to do any magic - we just have to skip past the header and read real data right after it. Note that the size of the block may be 0xF00 and not 0x1000 so <= and skipping whatever offset you get is probably best course of action until the segments are bit better understood. If there are more segments, we can proceed with calculation of segment size (as the data is interleaved in a way described above). As mentioned before, we roughly know when each of LIP segments appears in the audio file (it is roughly equivalent to 4 seconds, leaving last block unaligned most of the time with smaller size). We should try to pattern match buffer of size 0x780 filled with zeroes, masking each found offset with ~0xFFF (which will left-align on 0x1000) and taking closest offset to the one we predicted. We then read in minimum from "data block bytes left to read" and this "found LIP segment offset", skip 0x1000 bytes to get "divider offset" for the encoded block and copy each part of the segment into its own buffer. In the end, we are left with complete LIP data and complete WAV data. Block of WAV data is organized in such a way that it has all non-distance-based entries at the beginning and all distance-based entries at the end. There is no clear block of LIP data, it seems to be mixed randomly in-between all of the entries so no reliable distinction in the block. Distance-based entries point to same data offset, there are always exactly three such pointers (2 defined in *.WHD which have their copy in *.STR file also, 1 is only defined in *.STR file). There cannot be other number of "duplicates" pointing to same data offset than 1 (none) or 3 (distance-based entry, 1 for master and 2 for near/far data). Third entry we mentioned is STR file only, it is the true data definition used by the sound graph. If LIP data is present, master record has appropriate flag set. Note that master record should not really be used for other things, as its parameters are not exactly the same always and correct ones are located directly in the entry. TODO - add information about distance-based records structure, it is also interleaved... Due to all this, recommended way to get to the actual data is to pre-calculate all individual WAV data block sizes and resolve LIP segment sizes for each record along with detecting which records are distance-based. TODO - add parsing process used by Glacier 1 Audio Tool which seems to have correct export Note that format information of data, along with data sizes, offsets, names, etc. are all the same as one can find in their equivalent records in *.WHD files. So there is no need to reference *.WHD files for any sort of information for extraction of the *.STR files (unlike older Glacier 1 games). Below are simple C++ headers which should help anyone interested to get started with the file format I hope! V1 is for Hitman: Blood Money V2 is for Kane & Lynch: Dead Men and Mini Ninjas V3 is for Kane & Lynch 2: Dog Days (TODO - missing information+header!) // // Created by Andrej Redeky. // SPDX-License-Identifier: Unlicense // // Extended format information: https://reshax.com/topic/27-glacier-1-str-file-format // #pragma once enum class STR_LanguageID_v1 : uint32_t { Default = 0, English = 1, German = 2, French = 3, Spanish = 4, Italian = 5, Dutch = 6 }; struct STR_Header_v1 { char id[0xC] = {'I', 'O', 'I', 'S', 'N', 'D', 'S', 'T', 'R', 'E', 'A', 'M'}; // always "IOISNDSTREAM" uint8_t unkC[0x4]; // always seems to be a sequence 09 00 00 00 uint32_t offsetToEntryTable = 0; // points at the STR_Footer, right after string table ends uint32_t entriesCount = 0; // same as number of STR_Data entries in STR_Footer uint32_t dataBeginOffset = 0x100; // offset to beginning of data probably, but it is like this even for PC_Eng.str which does not have such size and has no data... uint8_t unk1C[0x8]; // always seems to be a sequence 00 00 00 00 01 00 00 00 STR_LanguageID_v1 languageId = STR_LanguageID_v1::Default; // specifies which language data is contained within the archive }; enum class STR_DataFormat_v1 : uint32_t { INVALID = 0x00, PCM_S16 = 0x02, IMA_ADPCM = 0x03, OGG_VORBIS = 0x04, DISTANCE_BASED_MASTER = 0x11 }; // beware that this is really 3 different headers, as there is no padding... didn't know how to name things so left it like this for now.. struct STR_DataHeader_v1 { // PCM_S16, IMA_ADPCM, OGG_VORBIS and DISTANCE_BASED_MASTER have following bytes STR_DataFormat_v1 format; // specifies how data should be read uint32_t samplesCount; // samples count uint32_t channels; // number of channels uint32_t sampleRate; // sample rate uint32_t bitsPerSample; // bits per sample // all PCM_S16, IMA_ADPCM and DISTANCE_BASED_MASTER have following bytes on top uint32_t blockAlign; // block alignment // all IMA_ADPCM have following bytes on top uint32_t samplesPerBlock; // samples per block }; struct STR_Entry_v1 { uint64_t id; // probably some ID, is less than total entries count, does not match its index uint64_t dataOffset; // offset to beginning of data, beware of the distance-based records which alias the same index! uint64_t dataSize; // data size uint64_t dataHeaderOffset; // offset to table containing header uint32_t dataHeaderSize; // size of STR_DataHeader_v1 (unused fields from the structure are left out) uint32_t unk24; // unknown number uint64_t fileNameLength; // length of filename in string table uint64_t fileNameOffset; // offset to filename in string table uint32_t hasLIP; // 0x04 when LIP data is present for current entry, 0x00 otherwise uint32_t unk3C; // unknown number uint64_t distanceBasedRecordOrder; // if 0, entry is not distance-based, otherwise denotes data order of individual records in data block (or is simply non-zero for master record) }; enum class STR_LanguageID_v2 : uint32_t { Default = 0, English = 1, German = 2, French = 3, Spanish = 4, Italian = 5, Dutch = 6 }; struct STR_Header_v2 { char id[0xC] = {'I', 'O', 'I', 'S', 'N', 'D', 'S', 'T', 'R', 'E', 'A', 'M'}; // always "IOISNDSTREAM" uint8_t unkC[0xC]; // always seems to be a sequence 09 00 00 00 XX XX YY YY 00 00 00 00 where XX XX changes with language and game and YY YY is same for a game (Kane & Lynch: Dead Man has this sequence E1 46, Mini Ninjas has this sequence 4C 4A) uint32_t offsetToEntryTable = 0; // points at the STR_Footer, right after string table ends uint32_t entriesCount = 0; // same as number of STR_Data entries in STR_Footer uint32_t dataBeginOffset = 0x100; // offset to beginning of data probably, but it is like this even for PC_Eng.str which does not have such size and has no data... uint8_t unk24[0x8]; // always seems to be a sequence 00 00 00 00 01 00 00 00 STR_LanguageID_v2 languageId = STR_LanguageID_v2::Default; // specifies which language data is contained within the archive uint8_t unk30[0x8]; // always some sequence 38 XX XX XX XX XX XX XX where XX is same for a game (Kane & Lynch: Dead Man has this sequence 00 A1 01 18 EE 90 7C, Mini Ninjas has this sequence 00 00 00 00 00 00 00) }; enum class STR_DataFormat_v2 : uint32_t { INVALID = 0x00, PCM_S16 = 0x02, IMA_ADPCM = 0x03, OGG_VORBIS = 0x04, UNKNOWN_MASTER = 0x1A }; // beware that this is really 2 different headers, as there is no padding... didn't know how to name things so left it like this for now.. struct STR_DataHeader_v2 { // PCM_S16, IMA_ADPCM, OGG_VORBIS and UNKNOWN_MASTER have following bytes STR_DataFormat_v2 format; // specifies how data should be read uint32_t samplesCount; // samples count uint32_t channels; // number of channels uint32_t sampleRate; // sample rate uint32_t bitsPerSample; // bits per sample uint32_t unk14 = 0; uint32_t unk18 = 0; uint32_t blockAlign; // block alignment // all IMA_ADPCM have following bytes on top uint32_t samplesPerBlock; // samples per block }; struct STR_Entry_v2 { uint64_t id; // probably some ID, is less than total entries count, does not match its index uint64_t dataOffset; // offset to beginning of data, beware of the distance-based records which alias the same index! uint64_t dataSize; // data size uint64_t dataHeaderOffset; // offset to table containing header uint32_t dataHeaderSize; // size of STR_DataHeader_v2 (unused fields from the structure are left out) uint32_t unk24; // unknown number uint64_t fileNameLength; // length of filename in string table uint64_t fileNameOffset; // offset to filename in string table uint32_t hasLIP; // 0x04 when LIP data is present for current entry, 0x00 otherwise uint32_t unk3C; // unknown number uint64_t unk40; // OLD INFO: if 0, entry is not distance-based, otherwise denotes data order of individual records in data block (or is simply non-zero for master record) };1 point
ResHax.com: Empowering Curious Minds in the World of Reverse Engineering
Delving into the Art of Code Unraveling: ResHax.com - Your Gateway to the Thrilling World of Reverse Engineering, Where Curiosity Meets Innovation!