Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation since 04/17/2025 in all areas

  1. Python code to extract the texts: import struct, codecs BIN = "/mnt/data/menu_decompress.dat" OUT = "/mnt/data/menu_decompress_output.txt" be16 = lambda b, o: struct.unpack_from(">H", b, o)[0] le32 = lambda b, o: struct.unpack_from("<I", b, o)[0] be32 = lambda b, o: struct.unpack_from(">I", b, o)[0] def read_wstr_be(buf, off): raw = bytearray() while off + 1 < len(buf): val = be16(buf, off) off += 2 if val == 0: break raw += val.to_bytes(2, "big") return raw.decode("utf-16-be", errors="replace") with open(BIN, "rb") as f: data = f.read() p = 8 name_len = le32(data, p) p += 4 + name_len + 1 p += 1 + 4 + 4 + 20 + 4 + 8 base = p max_num = be16(data, p) p += 2 count = be16(data, p) p += 2 data_pos = p p += count * 4 num_blocks = be16(data, p) p += 2 tbl = p memo, visiting = {}, set() def tex(idx): if idx == 0: return "" if idx in memo: return memo[idx] if idx in visiting: memo[idx] = "" return "" visiting.add(idx) node_off = data_pos + idx * 4 if node_off + 3 >= len(data): visiting.remove(idx) memo[idx] = "" return "" right = be16(data, node_off) left = be16(data, node_off + 2) s = read_wstr_be(data, node_off) if left == 0 else tex(left) + tex(right) visiting.remove(idx) memo[idx] = s return s out = [] ptr = tbl for _ in range(num_blocks): if ptr + 12 > len(data): break data_u = be32(data, ptr + 4) + base loc = be32(data, ptr + 8) + base + 2 ptr += 12 if loc - 2 >= len(data): continue rec_cnt = be16(data, loc - 2) pos_tmp = data_u for r in range(rec_cnt + 1): ent_off = loc + r * 4 if ent_off + 1 >= len(data): break end = be16(data, ent_off) + data_u if end > len(data): break pos = pos_tmp buf = [] while pos < end: b = data[pos] if b < max_num: buf.append(tex(b + 1)) pos += 1 elif b < 255: val = be16(data, pos) - 255 * max_num buf.append(tex(val + 1)) pos += 2 else: # b == 255 val = be16(data, pos + 1) buf.append(tex(val + 1)) pos += 3 out.append("".join(buf)) pos_tmp = pos with codecs.open(OUT, "w", "utf-8") as f: f.write("\n".join(out)) I can also work on repack if I find spare time.
    5 points
  2. 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%
    4 points
  3. Mod released: https://www.nexusmods.com/horizonforbiddenwest/mods/113
    4 points
  4. Ok, now my tool works fine with the original files. Usage: Run ExportText.exe, select *.cat file. BleachRoS_cat_ExportToolByGier.7z If you just need to decompress files, then use this script: comtype zlib get NAME basename get id longlong get SIZE long get ZERO long get OFFSET long get ZSIZE asize math ZSIZE - OFFSET string NAME += "_dec.cat" clog NAME OFFSET ZSIZE SIZE
    3 points
  5. Version 1.0.0

    261 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.
    3 points
  6. Oh, here is my script. It is based in other script that I made before. A lot of PS2 games use that mesh format with tags, I just changed a few things here and there: fmt_legend_of_spyro_ps2_mdg.py
    2 points
  7. 2 points
  8. Small World is it? I was helping a spanish guy in Xentax discord about this exactly game a few days ago. I made a tool to unpack/pack descompress/compress LZSS compression. The tool is in Modern Portuguese, my native language, but is easy to understand. GsS_Lzss_Tool_v2.zip
    2 points
  9. Hello, for the Guardian_Fire dragon Dave's (DKDave?, dunno) script collects 997 small vertex chunks with 3 to 16 vertices, afaics. First 0080026C block has 11 elements in each sub block (0B000000 after first signature 0080026C). Vertex block signature is 02800B68, maybe the 0B is the count again (as it's known from vif signatures). I've rectangular bordered the first and last vertex of the first chunk in black, the green block seems to contain the uv data as shorts, tx, ty, unk1, unk2, so 11x8 bytes . edit: uvs ok so far (I think), except the for the garbage at the lower left corner:
    2 points
  10. O.K, here's Noesis script. Not sure how to assign material + texture to it. from inc_noesis import * import noesis import rapi import os def registerNoesisTypes(): handle = noesis.register("Bright Light", ".ea3") noesis.setHandlerTypeCheck(handle, noepyCheckType) noesis.setHandlerLoadModel(handle, noepyLoadModel) noesis.logPopup() return 1 def noepyCheckType(data): bs = NoeBitStream(data) if len(data) < 20: return 0 return 1 def noepyLoadModel(data, mdlList): bs = NoeBitStream(data) baseName = rapi.getExtensionlessName(rapi.getLocalFileName(rapi.getInputName())) ctx = rapi.rpgCreateContext() Underline = "_" # Header Start bs.read(4) # Sign TotalFileSize = bs.readUInt() MatrixOffset = bs.readUInt() MeshDataBaseOffset = bs.readUInt() # Base Offset bs.read(16) Matrix4x4 = bs.read(64) BBox = bs.read(32) Unknown_0 = bs.readUInt() UnknownOffset = bs.readUInt() MaterialIndex = bs.readUInt() MaterialIndexOffset = bs.readUInt() TextureCount = bs.readUInt() MaterialInfoOffset = bs.readUInt() Unknown_1 = bs.readUInt() Unknown_2 = bs.readUInt() BoneMatrixOffset = bs.readUInt() Unknown_3 = bs.readUInt() Unknown_4 = bs.readUInt() Unknown_5 = bs.readUInt() Unknown_6 = bs.readUInt() Unknown_7 = bs.readUInt() RootOffset = bs.readUInt() Unknown_8 = bs.readUInt() # Header End MaterialPropOffsetList = [] TextureIndexList = [] TextureNameList = [] ShapeInfoOffsetList = [] MaterialIndexList = [] bs.seek(MaterialIndexOffset, NOESEEK_ABS) for i in range(0, MaterialIndex): MaterialPropOffsetList.append(bs.readUInt()) TextureMapCount = bs.readUByte() bs.read(11) for i in range(0, MaterialIndex): MaterialPropOffset = MaterialPropOffsetList[i] bs.seek(MaterialPropOffset, NOESEEK_ABS) TextureIndexList.append(bs.readUByte()) bs.read(7) bs.seek(MaterialInfoOffset, NOESEEK_ABS) TextureNameOffset = bs.readUInt() bs.seek(TextureNameOffset, NOESEEK_ABS) for j in range(0, TextureCount): TextureNameList.append(bs.readString()) TextureName = TextureNameList[j] bs.seek(UnknownOffset, NOESEEK_ABS) bs.read(80) MeshIndex = bs.readUInt() bs.read(4) MeshInfoOffset = bs.readUInt() bs.seek(MeshInfoOffset, NOESEEK_ABS) for k in range(0, MeshIndex): BBox = bs.read(32) ShapeInfoOffsetList.append(bs.readUInt()) Unknown_0 = bs.readUInt() UnknownOffset = bs.readUInt() MaterialIndexList.append(bs.readUByte()) bs.read(3) for k in range(0, MeshIndex): ShapeIndexDigfmt = "{:04d}".format(k) ShapeInfoOffset = ShapeInfoOffsetList[k] MaterialIndex = MaterialIndexList[k] bs.seek(ShapeInfoOffset, NOESEEK_ABS) bs.read(20) IndexOffset = bs.readUInt() + MeshDataBaseOffset bs.read(10) IndexCount = bs.readUShort() Stride = bs.readUByte() Unknown_9 = bs.readUByte() VertexCount = bs.readUShort() bs.read(24) VertexBuffer = bs.readBytes(VertexCount * Stride) bs.seek(IndexOffset, NOESEEK_ABS) IndexBuffer = bs.readBytes(IndexCount * 2) rapi.rpgBindPositionBufferOfs(VertexBuffer, noesis.RPGEODATA_FLOAT, Stride, 0) rapi.rpgBindUV1BufferOfs(VertexBuffer, noesis.RPGEODATA_FLOAT, Stride, 24) rapi.rpgBindNormalBufferOfs(VertexBuffer, noesis.RPGEODATA_FLOAT, Stride, 12) rapi.rpgSetName(baseName + Underline + ShapeIndexDigfmt) rapi.rpgSetMaterial(TextureNameList[TextureIndexList[MaterialIndexList[k]]]) rapi.rpgCommitTriangles(IndexBuffer, noesis.RPGEODATA_USHORT, IndexCount, noesis.RPGEO_TRIANGLE_STRIP) mdl = rapi.rpgConstructModel() mdlList.append(mdl) return 1
    2 points
  11. At least he left my name on it - some don't 🙂
    2 points
  12. it uses a rather simple LZSS compression. I haven't tested it myself because I don't have these new graphics tools, and I don't even know where to open the file! LOL if you can test the tool attached if works or not. """ Descompressor PCMP → LZSS -------------------------------------------------------------""" from __future__ import annotations import struct, argparse from pathlib import Path import tkinter as tk from tkinter import filedialog as fd, messagebox as mb DATA_OFFSET = 0x20 # início dos dados comprimidos # --------------------------------------------------------------------------- # SIMPLE HEADER # --------------------------------------------------------------------------- def _read_header(buf: bytes): if len(buf) < DATA_OFFSET: raise ValueError("Arquivo muito curto para cabeçalho PCMP.") if buf[:4] != b"PCMP": raise ValueError("Assinatura 'PCMP' não encontrada.") out_size, comp_size = struct.unpack_from("<II", buf, 0x14) remaining = len(buf) - DATA_OFFSET if comp_size == 0 or comp_size > remaining: comp_size = remaining # fallback: usa o resto do arquivo return out_size, comp_size # --------------------------------------------------------------------------- # LZSS # --------------------------------------------------------------------------- def _lzss_simple(comp: memoryview, out_size: int) -> bytes: out = bytearray() idx = 0 ilen = len(comp) if ilen == 0: raise EOFError("Fluxo comprimido vazio.") b = comp[idx]; idx += 1 bits = 8 while len(out) < out_size: if idx > ilen: raise EOFError("Fluxo comprimido terminou antes do previsto.") op = 1 if (b & 0x80) else 0 # 1 = cópia, 0 = literal b <<= 1 bits -= 1 if bits == 0: if idx >= ilen: break # não há mais flags b = comp[idx]; idx += 1 bits = 8 if op: if idx + 1 >= ilen: raise EOFError("Fim prematuro lendo bloco de cópia.") d = ((comp[idx] >> 4) | (comp[idx + 1] << 4)) + 1 # deslocamento cnt = (comp[idx] & 0x0F) + 3 # comprimento idx += 2 for _ in range(cnt): if len(out) >= out_size: break if d > len(out): out.append(0) else: out.append(out[-d]) else: # literal if idx >= ilen: raise EOFError("Fim prematuro lendo literal.") out.append(comp[idx]); idx += 1 return bytes(out) # --------------------------------------------------------------------------- # Função principal # --------------------------------------------------------------------------- def decompress_pcmp(data: bytes) -> bytes: out_sz, comp_sz = _read_header(data) return _lzss_simple(memoryview(data[DATA_OFFSET: DATA_OFFSET + comp_sz]), out_sz) # --------------------------------------------------------------------------- # CLI & GUI enxutos # --------------------------------------------------------------------------- def _decompress_file(src: Path, dst: Path): dst.write_bytes(decompress_pcmp(src.read_bytes())) print(f"Descomprimido → {dst}") def _cli(argv=None): ap = argparse.ArgumentParser(description="Descompressor PCMP (comtype YAKUZA)") ap.add_argument("input", nargs="?", help="Arquivo PCMP de entrada") ap.add_argument("output", nargs="?", help="Arquivo BIN de saída") ap.add_argument("--nogui", action="store_true", help="Forçar modo terminal") ar = ap.parse_args(argv) if ar.nogui or (ar.input and ar.output): if not (ar.input and ar.output): ap.error("Informe entrada e saída ou utilize a GUI.") _decompress_file(Path(ar.input), Path(ar.output)) else: _launch_gui() class _App(tk.Tk): def __init__(self): super().__init__() self.title("PCMP → BIN (lzss)") self.geometry("500x160") self.in_path = tk.StringVar(self); self.out_path = tk.StringVar(self) tk.Label(self, text="Arquivo PCMP:").grid(row=0, column=0, sticky="e", padx=10, pady=10) tk.Entry(self, textvariable=self.in_path, width=45).grid(row=0, column=1, padx=5) tk.Button(self, text="…", width=3, command=self._choose_in).grid(row=0, column=2, padx=5) tk.Label(self, text="Salvar como:").grid(row=1, column=0, sticky="e", padx=10, pady=10) tk.Entry(self, textvariable=self.out_path, width=45).grid(row=1, column=1, padx=5) tk.Button(self, text="…", width=3, command=self._choose_out).grid(row=1, column=2, padx=5) tk.Button(self, text="Descomprimir", width=15, command=self._run).grid(row=2, column=1, pady=20) def _choose_in(self): p = fd.askopenfilename(title="Selecione o arquivo PCMP", filetypes=[("TXB", "*.txb"), ("Todos", "*.*")]) if p: self.in_path.set(p) if not self.out_path.get(): self.out_path.set(Path(p).with_suffix(".dec")) def _choose_out(self): p = fd.asksaveasfilename(title="Salvar arquivo descomprimido", defaultextension=".bin", filetypes=[("DEC", "*.dec"), ("Todos", "*.*")]) if p: self.out_path.set(p) def _run(self): if not self.in_path.get(): mb.showwarning("Atenção", "Selecione um arquivo de entrada.") return if not self.out_path.get(): mb.showwarning("Atenção", "Selecione um local de saída.") return try: _decompress_file(Path(self.in_path.get()), Path(self.out_path.get())) mb.showinfo("Sucesso", "Descompressão concluída!") except Exception as exc: mb.showerror("Erro", str(exc)) def _launch_gui(): _App().mainloop() # --------------------------------------------------------------------------- if __name__ == "__main__": _cli() tool._TXB.py
    2 points
  13. No updated as yet. Haven't looked at it for a few days, but I'll have another look at it soon and upload an updated script.
    2 points
  14. I was able to rewrite PS2 4-bit swizzle code to Python and add it to ReverseBox v0.30.0. Enjoy: https://github.com/bartlomiejduda/ReverseBox/blob/main/reversebox/image/swizzling/swizzle_ps2_4bit.py
    2 points
  15. Firstly, don't open files in notepad - you need a proper hex editor at the very least to see the proper information. Most files won't have anything recognisable, that's where experience of reverse engineering comes in to recognise data offsets, different types of data, etc. You might get some files that are fairly "standard", such as DDS image files or RIFF/WAVE audio files, but most will be proprietary. Sometimes you might get lucky and versions of the file may have been used in previous games and others have already looked at them. Even just editing text can be tricky. For example, if your text is larger than the existing text, it may affect offsets for other lines of text, and you would also have to amend all of those affected. If your text is smaller, it may work. If you're doing something like translating to another language, it may not have the font data for specific symbols. A lot of things to consider, even for seemingly simple tasks.
    2 points
  16. I've been looking into the materials a bit more and made some progress. Here's a few test renders. I'm not sure if some UVs are exactly right, but it's better than no textures.
    2 points
  17. I checked the wav file (WAVS) that was unzipped from the compressed file and found that the value represented by the file size is counted after the file size, not from the flag (RIFF), but the flag and size must exist. I updated the script to make sure the extracted file size is counted after these eight bytes to make sure the extracted .wav file is complete In addition, the WAVS of the file is also modified to WAVE by the way Because it is not modified, vgmstream cannot be converted Convert with vgmsstream https://vgmstream.org/ https://github.com/vgmstream/vgmstream By the way, you can run the script like this python saf.py saf file output folder saf.py
    2 points
  18. They are *almost* normal wave files. For some reason the type is "WAVS" and not "WAVE" that prevents them playing. It uses XBox ADPCM which will play fine in vgmstream, but the "WAVS" needs to be changed to "WAVE" in each file.
    2 points
  19. For .saf files try to extract with this python script, if there is no problem you will get a bunch of .wav files saf.py
    2 points
  20. These ones? From geomchr006. I think I've just about got the basic mesh format sorted.
    2 points
  21. 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.py
    2 points
  22. 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/63
    2 points
  23. yes, need latest BMS version and only quickbms_4gb_files.exe all attachments below AES_finder_0.9h.zip AES_finder_mobile_0.9e.zip custom_UE4_scripts.zip sound_extraction_UE4.zip UE4_specific_scripts.zip unreal_tournament_4_0.4.25e.zip unreal_tournament_4_0.4.27e.zip
    2 points
  24. @morrigan Technically, you can multiplex PSS with AC3 audio just fine as a data-type stream even with the old v1.00 build of ps2str from 1999, but that version of the tool cannot properly mux VBR m2v, so it's kind of borderline useless. 2001-ish to the latest PS2 SDKs include ps2str v1.08, which is widely available on its own these days. @P-chan Mux the AC3 track as a data stream via ps2str v1.08, just be sure to set its bitrate to 56000 bytes/sec (assuming that it's a standard 448 kbps AC3).
    1 point
  25. Version 1.5.0

    3 downloads

    With this tool, you will be able to decompress and compress, unpack and pack the .bin files related to the game's texts and maybe graphics.
    1 point
  26. There was a ProjectG1M project (on xentax?) which I could generate a .dll from, April 2024. Drag&drop of g1m onto Noesis gave me this: Sadly I don't remember much so you'll need to search in xentax archives for more infos. edit: Seems it was Joschka's project, compare here, Zenhax archive Joschuka, github
    1 point
  27. Idk what do you mean by "two" but check out my Sketchfab account maybe you can find something usefull here - https://sketchfab.com/Deduska_1947/models
    1 point
  28. Great! Good luck on your translation.
    1 point
  29. End of guess work guys...😉 Hope you are familiar with 010 HEX Editor Templates... You can easily rewrite it to the Noesis form. Or maybe I can... Found only 32 / 36 stride type in samples you provide. There may be more I guess. Template parse only 32 / 36 stride. //------------------------------------------------ //--- 010 Editor v14.0 Binary Template // // File: // Authors: // Version: // Purpose: // Category: // File Mask: // ID Bytes: // History: //------------------------------------------------ LittleEndian();OutputPaneClear(); local uint32 i,j,k,l,m,n; char Sign[4]; uint32 TotalFileSize; uint32 MatrixOffset; uint32 MeshDataBaseOffset; FSkip(16); float Matrix4x4[16]; float BBox[8]; uint32 Unknown_0, UnknownOffset, MaterialIndex, MaterialIndexOffset, TextureCount, MaterialInfoOffset, Unknown_1, Unknown_2, BoneMatrixOffset, Unknown_3, Unknown_4, Unknown_5, Unknown_6, Unknown_7, RootOffset, Unknown_8; FSeek(MaterialIndexOffset); struct { struct { uint32 MaterialPropOffset; ubyte TextureMapCount,Unknown_1,Unknown_2,Unknown_3; ubyte Unknown_4,Unknown_5; uint16 Unknown_6; uint32 Unknown_7; }MaterialInfo[MaterialIndex]<optimize=false>; struct { for (i=0; i < MaterialIndex; i++) { FSeek(MaterialInfo[i].MaterialPropOffset); struct { ubyte TextureIndex; ubyte Unknown_0,Unknown_1,Unknown_2; float Unknown_3; }MaterialProp; } }MaterialProperties; FSeek(MaterialInfoOffset); uint32 TextureNameOffset; FSeek(TextureNameOffset); struct { string TextureName; }Texture[TextureCount]<optimize=false>; }MaterialTable; FSeek(UnknownOffset); struct { float Unknown_0[8]; uint32 Unknown_1[4]; float Unknown_2[8]; }UnknownData; struct { uint32 MeshIndex; uint32 Unknown_16,MeshInfoOffset; FSeek(MeshInfoOffset); struct { float BBox[8]; uint32 ShapeInfoOffset, Unknown_0, UnknownOffset; ubyte MaterialIndex, Unknown_1, Unknown_2, Unknown_3; }MeshInfo[MeshIndex]<optimize=false>; }MeshTable; if (BoneMatrixOffset != 0) { FSeek(BoneMatrixOffset); struct { uint32 BoneCount; uint32 BoneNameInfoOffset, BoneParentOffset, Unknown_2, Unknown_3, BoneIndexOffset, BoneIndex, Unknown_6; struct { float Mat00,Mat01,Mat02,Mat03, Mat10,Mat11,Mat12,Mat13, Mat20,Mat21,Mat22,Mat23, Mat30,Mat31,Mat32,Mat33; }BonePosition[BoneCount]<optimize=false>; FSeek(BoneNameInfoOffset); struct { uint32 BoneNameOffset; local uint32 cPos=FTell(); FSeek(BoneNameOffset); string BoneName; FSeek(cPos); }BoneNames[BoneCount]<optimize=false>; FSeek(BoneParentOffset); ubyte BoneParent[BoneCount]; FSeek(RootOffset); char RootName[8]; ubyte Count; ubyte RootIndex[Count]; }BoneTable; } for (i=0; i < MeshTable.MeshIndex; i++) { FSeek(MeshTable.MeshInfo[i].ShapeInfoOffset); struct { char Sign[2]; ubyte Unknown_0; ubyte Unknown_1; uint16 Unknown_2; uint16 Unknown_3; uint32 Unknown_4; uint32 MeshDataSize; uint32 VertexOffset; uint32 IndexOffset; uint32 Unknown_5; uint16 Unknown_6; uint16 Unknown_7; uint16 Unknown_8; uint16 IndexCount; ubyte Stride; ubyte Unknown_9; uint16 VertexCount; FSkip(24); struct { float VPosX,VPosY,VPosZ; float VNPosX,VNPosY,VNPosZ; float UVPosX,UVPosY; if (Stride == 36) float Unknown; }VertexBuffer[VertexCount]<optimize=false>; FSeek(MeshDataBaseOffset + IndexOffset); struct { uint16 Index; }Indices[IndexCount]; }Mesh; }
    1 point
  30. Damn, I made a text export tool back in 2024... The structure of the file is quite simple, but code for importing text is a pain. SongsofSilenceTextToolByGier.7z The tool only works with the provided files. Usage: Export text: Run ExportText.exe, select loca_[lang].assets file Import text: Run ImportText.exe, select translated file
    1 point
  31. yes, the file has been modded by hungarian fans. here is the original file below. I hope, you can help me Bleach_Text.rar
    1 point
  32. What do you mean? It's standard JSON file, so you can load it directly to CAT program or use some "json converter" tool.
    1 point
  33. Games of that time usually have no indices. Coordinates are forming the faces of a model one by one.
    1 point
  34. Hey, still didn't figured it out. I have successfully parsed texture data + some static meshes wihout index. It's all in one so it's not easy define what is what without proper debug which is not my cup of tea.
    1 point
  35. Hell yes! An epic conclusion 🚬🦾😎 If you're the sharing type hmu on Instagram, would love to make some low poly sopranos content 😂🙏 Zachx.gif
    1 point
  36. I've had a quick look at the first archive. I don't know the exact format of the file tables and how they relate filenames to the actual files, but I can see that it uses LZ4 compression, so the raw files can be easily extracted without names. Here's an example image extracted manually as a test. If I can work out the filename/file relationship, a script should be easy enough.
    1 point
  37. damn, my bad then. thx for correction Also as for manifest.7z requiring a password, it's "Thank you!"
    1 point
  38. Did the tool work? can you mark as solutiion? its the minimal.
    1 point
  39. Some of the files you provide contain zstd compression File Type 1:89 6A 74 65 78 20 78 0D Compression position is 0x5B But there are 22 bytes of additional data before this These additional data need to be added to the compressed file at the same time and skipped when decompressed 4 bytes - extra data + total compressed data size 4 bytes - unknown, very close to the size of the compressed data 4 bytes - Decompressed size (correct) File Type 2:89 6A 6D 6F 78 0D 0A 01 The example of this file starts after 510 bytes Because two unknowns need to be read, four compression sizes and four decompressed sizes File Type 3:02 AF EA 61 6E 67 69 65 79 EC DC 47 The verification is too small, and I haven't seen whether there is compression for the time being, I'm not sure For the first two data, I wrote a script to decompress the compressed data inside and directly extract binary data of unknown structures. All research is based on guessing from existing files, if a more complete script is needed, you may need to add features based on more files, because I can't guarantee that the offsets displayed by each file are the same import os import struct import pyzstd import random import string from pathlib import Path def random_filename(length=16, extension='.bin'): chars = string.ascii_letters + string.digits return ''.join(random.choice(chars) for _ in range(length)) + extension def process_first_format(f): extracted_data = [] f.seek(69) while True: position = f.tell() size_bytes = f.read(4) if len(size_bytes) < 4: break data_size = struct.unpack('<I', size_bytes)[0] remaining_size = data_size - 4 data = f.read(remaining_size) if len(data) < remaining_size: print(f"Insufficient data @ {position}") break full_data = size_bytes + data processed_data = full_data[22:] if len(full_data) > 22 else full_data if len(processed_data) >= 4 and processed_data[:4] == bytes.fromhex('28 B5 2F FD'): try: extracted_data.append(pyzstd.decompress(processed_data)) except Exception as e: print(f"Decompression failed: {e}") extracted_data.append(processed_data) else: extracted_data.append(processed_data) return extracted_data def process_second_format(f): extracted_data = [] f.seek(510) while True: position = f.tell() unknown = f.read(2) if len(unknown) < 2: break compression_info = f.read(8) if len(compression_info) < 8: break compressed_size = struct.unpack('<I', compression_info[:4])[0] compressed_data = f.read(compressed_size) if len(compressed_data) < compressed_size: print(f"Insufficient data @ {position}") break if len(compressed_data) >= 4 and compressed_data[:4] == bytes.fromhex('28 B5 2F FD'): try: extracted_data.append(pyzstd.decompress(compressed_data)) except Exception as e: print(f"Decompression failed: {e}") extracted_data.append(compressed_data) else: extracted_data.append(compressed_data) return extracted_data def process_single_file(input_file, output_dir): try: with open(input_file, 'rb') as f: header = f.read(8) if header == bytes.fromhex('89 6A 74 65 78 20 78 0D'): print(f"Processing {input_file} (Format 1)") results = process_first_format(f) elif header == bytes.fromhex('89 6A 6D 6F 78 0D 0A 01'): print(f"Processing {input_file} (Format 2)") results = process_second_format(f) else: print(f"Skipping {input_file} (Unknown format)") return for i, data in enumerate(results, 1): output_path = Path(output_dir) / random_filename() with open(output_path, 'wb') as out_file: out_file.write(data) print(f"Saved block {i} from {input_file}") except Exception as e: print(f"Error processing {input_file}: {e}") def process_directory(input_dir, output_dir): Path(output_dir).mkdir(parents=True, exist_ok=True) for entry in os.scandir(input_dir): if entry.is_file(): process_single_file(entry.path, output_dir) if __name__ == '__main__': import sys if len(sys.argv) != 3: print("Usage: python asphalt.py <input_directory> <output_directory>") sys.exit(1) process_directory(sys.argv[1], sys.argv[2])
    1 point
  40. 1 download

    This is attachment from ZenHAX posted by Riesz in topic: Idea Factory CL3 and TID
    1 point
  41. Dear Reshax community, We’re grateful to have you as part of the Reshax family, and we hope you’ve found value in the discussions and resources our forum provides. As you may be aware, maintaining an active online community comes with costs—particularly for hosting and the necessary licenses to keep the forum running smoothly and securely. For almost a year, we’ve been covering these expenses ourselves to ensure the forum remains accessible to all. However, as the community continues to grow, so do the associated costs. We need your support to help cover the expenses for hosting services and licensing fees, which are essential for keeping the forum operational and maintaining its quality. Any contribution, no matter how small, will go directly towards these costs, allowing us to continue providing a reliable and engaging platform for everyone. Your support will help us ensure that the forum remains a place for learning, sharing, and connecting without disruption. We understand that not everyone is in a position to contribute, but if you are able to help, it would be greatly appreciated and would make a significant difference in keeping Reshax running strong. Thank you for being a part of our community and for considering supporting us. Together, we can sustain Reshax as a valuable resource for all. DONATION : https://reshax.com/clients/donations/1-license-and-hosting-cost/ Your Reshax team forever :)
    1 point
  42. 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.zip
    1 point
  43. Version 1.0.1

    213 downloads

    Final Fantasy VII Rebirth tool Exports static and skeletal characters, maps (including nanites). In addition to all DLLs provided here, it requires global.ucas file from the game, it must be in the same folder as .EXE. Usage is similar to previous FF7remake tool. See below. Main difference is that nanite maps (using nanites) are exported in DMF format (download blender plugin for it here). Tool usage: You can use the tool to convert individual assets, or all assets in a folder. 1. To convert one asset, drop asset file onto the tool, or run the tool from command line with asset filename as parameter. If asset file contains a texture, it will create "texture.db" file with information for later use in map extraction. If asset file contains static or skeletal mesh, in will be exported to ASCII. Raw data will also be saved in "staticmesh.raw" & "skeletalmesh.raw" folders for later use in map extraction. If asset file contains a material, it will export material info in a text file, and create "material.db" file with information for later use in map extraction. If "material.db" file already exists next to the tool, it will be updated with newly found materials. If asset file contains a blueprint, it will create "blueprint.db" file with information for later use in map extraction. If "blueprint.db" file already exists next to the tool, it will be updated with newly found blueprints. 2. To convert all assets in a folder, place the tool in that folder and run it. Tool will search that folder and all subfolders for .uasset files and try converting them all. When prompted "are you sure?" you can press "enter" to confirm or Ctrl-C to cancel. 3. To convert maps, drop .umap file onto the tool, or run the tool from command line with umap filename as parameter. Current version will extract static meshes, skeletal meshes, blueprint-generated meshes and nanite "terrain streams" from a map. For map extraction to work, "staticmesh.raw" and "skeletalmesh.raw" folders with previously extracted data must be in the same folder with the tool. If some models will be missing, tool will give messages. If "blueprint.db" file will be next to the tool, it will try and place all models mentioned in blueprints on the map. There's no proper blueprint support, so some models may be placed incorrectly. If "texture.db" file will be next to the tool, it will be used to assign proper material names to nanite parts of a map instead of hashes.
    1 point
  44. Adding materials... thats probably last thing its missing before release
    1 point
  45. Version 0.4.2

    1,665 downloads

    Custom model import tool v0.4 beta + model export (from .model to .ascii) Usage: spiderman_2_pc_mi_04.exe <model> <ascii> <materials> You can use models previously converted form the game: export .model file with ModdingTool, then pass it to spiderman_2_pc_model.exe This will output a model in .ascii format, and materials.txt. Note that due to new LODs/lookgroups layout, export tool now exports ALL meshes with all LODs. You may want to delete lower LODs before importing back to game.
    1 point
  46. Version 1.0.0 [PC]

    290 downloads

    Usage is the same as PS4/PS5 tool, but you dont need oodle dll anymore. Change patch in .ini file, then run tool with asset package code. Example: h2_extr_pc.exe E264
    1 point
  47. Let's focus on one specific file that you're having trouble with, because the ones I tried work. e.g. 4bpp_swizzled_256x64_original -> unswizzled -> reswizzled. 9_256x64 4bpp unswizzled -> reswizzled -> unswizzled: --> --> 5_128x128 4bpp unswizzled -> reswizzled -> unswizzled: #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <memory.h> #include <stdbool.h> #include <stdint.h> #include <string.h> #include <limits.h> void Unswizzle4( uint8_t const* inputBuffer, // (width * height + 1) / 2 bytes uint8_t* outputBuffer, // (width * height + 1) / 2 bytes uint32_t width, uint32_t height, bool swizzle // reswizzle instead of unswizzle ); void Unswizzle8( uint8_t const* inputBuffer, // width * height bytes uint8_t* outputBuffer, // width * height bytes uint32_t width, uint32_t height, bool swizzle // reswizzle instead of unswizzle ); // nullptr was added in C23, but most compilers do not support it yet. #define nullptr NULL int main(int argc, char* argv[]) { if (argc < 8) { printf( "Usage:\n" " action bitdepth width height byteOffset inputFilename outputFilename\n" " action: swizzle, unswizzle\n" " bitdepth: 4, 8\n" " byte offset: 0 to any valid position in file\n" " file names: raw binary data\n" "\n" "e.g.:\n" " swizzle 4 256 64 0 input.dat output.dat\n" " unswizzle 8 128 128 0x40 input.bin output.bin\n" ); return EXIT_FAILURE; } char const* action = argv[1]; char const* bitDepthString = argv[2]; char const* widthString = argv[3]; char const* heightString = argv[4]; char const* byteOffsetString = argv[5]; char const* inputFilename = argv[6]; char const* outputFilename = argv[7]; bool swizzle = false; if (strcmp(action, "swizzle") == 0) { swizzle = true; } else if (strcmp(action, "unswizzle") == 0) { swizzle = false; } else { printf("Expect action \"swizzle\" or \"unswizzle\", not %s.", action); return EXIT_FAILURE; } uint32_t bitDepth = atoi(bitDepthString); if (bitDepth != 4 && bitDepth != 8) { printf("Expect bit depth of 4 or 8, not %s.", bitDepthString); return EXIT_FAILURE; } uint32_t width = atoi(widthString); if (width <= 0) { printf("Invalid width: %s.", widthString); return EXIT_FAILURE; } uint32_t height = atoi(heightString); if (height <= 0) { printf("Invalid height: %s.", heightString); return EXIT_FAILURE; } uint32_t byteOffset = strtoul(byteOffsetString, nullptr, 0); if (byteOffset == 0 && byteOffsetString[0] != '0') { printf("Invalid byte offset: %s.", byteOffsetString); return EXIT_FAILURE; } FILE* inputFile = fopen(inputFilename, "rb"); if (inputFile == nullptr) { printf("Could not open input file: \"%s\"", inputFilename); return EXIT_FAILURE; } FILE* outputFile = fopen(outputFilename, "wb"); if (outputFile == nullptr) { printf("Could not open output file: \"%s\"", outputFilename); return EXIT_FAILURE; } printf( "action: %s\n" "bit depth: %s\n" "width: %s\n" "height: %s\n" "byte offset: %s\n" "input: %s\n" "output: %s\n", action, bitDepthString, widthString, heightString, byteOffsetString, inputFilename, outputFilename ); // Allocate buffers. fseek(inputFile, 0, SEEK_END); long inputFileSize = ftell(inputFile); fseek(inputFile, 0, SEEK_SET); uint32_t minimumBufferSize = bitDepth * width * height / CHAR_BIT; if ((uint32_t)inputFileSize < byteOffset + minimumBufferSize) { inputFileSize = byteOffset + minimumBufferSize; } uint8_t* inputFileData = (uint8_t*)malloc(inputFileSize); uint8_t* outputFileData = (uint8_t*)malloc(inputFileSize); // Read input data, perform conversion, write output data. fread(inputFileData, 1, inputFileSize, inputFile); fclose(inputFile); // Copy data before and after the pixels. memcpy(outputFileData, inputFileData, byteOffset); uint32_t byteOffsetAfterPixels = byteOffset + minimumBufferSize; memcpy(outputFileData + byteOffsetAfterPixels, inputFileData + byteOffsetAfterPixels, inputFileSize - byteOffsetAfterPixels); switch (bitDepth) { case 4: Unswizzle4(inputFileData + byteOffset, outputFileData + byteOffset, width, height, swizzle); break; case 8: Unswizzle8(inputFileData + byteOffset, outputFileData + byteOffset, width, height, swizzle); break; } fwrite(outputFileData, 1, inputFileSize, outputFile); fclose(outputFile); free(inputFileData); free(outputFileData); printf("Converted\n"); return EXIT_SUCCESS; } void Unswizzle4( uint8_t const* inputBuffer, // (width * height + 1) / 2 bytes uint8_t* outputBuffer, // (width * height + 1) / 2 bytes uint32_t width, uint32_t height, bool swizzle ) { uint8_t* inputPixels8bpp = (uint8_t*)malloc(width * height); uint8_t* outputPixels8bpp = (uint8_t*)malloc(width * height); const uint32_t bufferByteSize = (width * height + 1) / 2; // Unpack 4-bpp pixels to 8-bpp. for (uint32_t i = 0; i < bufferByteSize; ++i) { uint8_t byteValue = inputBuffer[i]; uint8_t nybbleLow = byteValue & 0xF; uint8_t nybbleHigh = byteValue >> 4; inputPixels8bpp[i * 2] = nybbleLow; inputPixels8bpp[i * 2 + 1] = nybbleHigh; } // Swizzle/Unswizzle them as if 8-bpp. Unswizzle8(inputPixels8bpp, outputPixels8bpp, width, height, swizzle); // Repack 8-bpp pixels to 4-bpp. for (uint32_t i = 0; i < bufferByteSize; ++i) { uint8_t nybbleLow = outputPixels8bpp[i * 2 + 0]; uint8_t nybbleHigh = outputPixels8bpp[i * 2 + 1]; uint8_t byteValue = (nybbleHigh << 4) | nybbleLow; outputBuffer[i] = byteValue; } free(inputPixels8bpp); free(outputPixels8bpp); } void Unswizzle8( uint8_t const* inputBuffer, // width * height bytes uint8_t* outputBuffer, // width * height bytes uint32_t width, uint32_t height, bool swizzle ) { uint32_t pixelCount = width * height; for (uint32_t y = 0; y < height; y++) { for (uint32_t x = 0; x < width; x++) { uint32_t blockLocation = (y & (~0xF)) * width + (x & (~0xF)) * 2; uint32_t swapSelector = (((y + 2) >> 2) & 0x1) * 4; uint32_t posY = (((y & (~3)) >> 1) + (y & 1)) & 0x7; uint32_t columnLocation = posY * width * 2 + ((x + swapSelector) & 0x7) * 4; uint32_t byteNumber = ((y >> 1) & 1) + ((x >> 2) & 2); uint32_t swizzleOffset = blockLocation + columnLocation + byteNumber; uint32_t linearOffset = y * width + x; if (linearOffset < pixelCount && swizzleOffset < pixelCount) { if (swizzle) { outputBuffer[swizzleOffset] = inputBuffer[linearOffset]; } else { outputBuffer[linearOffset] = inputBuffer[swizzleOffset]; } } } } }
    1 point
  48. YES!! Thank you so much. You are the man/women. Also if anyone reading this is looking for a way to localize this game, I have Aluigi's QuickBMS script to export/import SDF archives. ubisoft_sdf.zip
    1 point
×
×
  • Create New...