├── .gitignore ├── BemaniLZ ├── BemaniLZ.c └── BemaniLZ.h ├── CMakeLists.txt ├── LICENSE ├── README.md ├── filedata-tool.py ├── lodepng ├── LICENSE ├── lodepng.c └── lodepng.h ├── pngquant ├── .gitignore ├── .travis.yml ├── CHANGELOG ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── COPYRIGHT ├── Cargo.toml ├── Makefile ├── README.md ├── blur.c ├── blur.h ├── configure ├── imagequant.pc.in ├── kmeans.c ├── kmeans.h ├── libimagequant.c ├── libimagequant.cs ├── libimagequant.h ├── mediancut.c ├── mediancut.h ├── mempool.c ├── mempool.h ├── nearest.c ├── nearest.h ├── org │ └── pngquant │ │ ├── Image.java │ │ ├── LiqObject.java │ │ ├── PngQuant.c │ │ ├── PngQuant.java │ │ ├── PngQuantException.java │ │ └── Result.java ├── pam.c ├── pam.h ├── pom.xml └── rust │ ├── build.rs │ └── libimagequant.rs ├── tcb-convert.c └── tcb-extract.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf -------------------------------------------------------------------------------- /BemaniLZ/BemaniLZ.c: -------------------------------------------------------------------------------- 1 | #include "BemaniLZ.h" 2 | 3 | /* Based on BemaniLZ.cs from Scharfrichter */ 4 | 5 | static const unsigned int BUFFER_MASK = 0x3FF; 6 | 7 | int decompress(uint8_t *src, int srcSize, uint8_t *dst, int dstSize) 8 | { 9 | int srcOffset = 0; 10 | int dstOffset = 0; 11 | 12 | uint8_t buffer[0x400]; 13 | int bufferOffset = 0; 14 | 15 | uint8_t data = '\0'; 16 | uint32_t control = 0; 17 | int32_t length = 0; 18 | uint32_t distance = 0; 19 | int loop = 0; 20 | 21 | while(1) 22 | { 23 | loop = 0; 24 | 25 | control >>= 1; 26 | if (control < 0x100) 27 | { 28 | control = (uint8_t)src[srcOffset++] | 0xFF00; 29 | //printf("control=%08X\n", control); 30 | } 31 | 32 | data = src[srcOffset++]; 33 | 34 | // direct copy 35 | // can do stream of 1 - 8 direct copies 36 | if ((control & 1) == 0) 37 | { 38 | //printf("%08X: direct copy %02X\n", dstOffset, data); 39 | dst[dstOffset++] = data; 40 | buffer[bufferOffset] = data; 41 | bufferOffset = (bufferOffset + 1) & BUFFER_MASK; 42 | continue; 43 | } 44 | 45 | // window copy (long distance) 46 | if ((data & 0x80) == 0) 47 | { 48 | /* 49 | input stream: 50 | 00: 0bbb bbaa 51 | 01: dddd dddd 52 | distance: [0 - 1023] (00aa dddd dddd) 53 | length: [2 - 33] (000b bbbb) 54 | */ 55 | distance = (uint8_t)src[srcOffset++] | ((data & 0x3) << 8); 56 | length = (data >> 2) + 2; 57 | loop = 1; 58 | //printf("long distance: distance=%08X length=%08X data=%02X\n", distance, length, data); 59 | //printf("%08X: window copy (long): %d bytes from %08X\n", dstOffset, length, dstOffset - distance); 60 | } 61 | 62 | // window copy (short distance) 63 | else if ((data & 0x40) == 0) 64 | { 65 | /* 66 | input stream: 67 | 00: llll dddd 68 | distance: [1 - 16] (dddd) 69 | length: [1 - 4] (llll) 70 | */ 71 | distance = (data & 0xF) + 1; 72 | length = (data >> 4) - 7; 73 | loop = 1; 74 | //printf("short distance: distance=%08X length=%08X data=%02X\n", distance, length, data); 75 | //printf("%08X: window copy (short): %d bytes from %08X\n", dstOffset, length, dstOffset - distance); 76 | } 77 | 78 | if (loop) 79 | { 80 | // copy length bytes from window 81 | while(length-- >= 0) 82 | { 83 | data = buffer[(bufferOffset - distance) & BUFFER_MASK]; 84 | dst[dstOffset++] = data; 85 | buffer[bufferOffset] = data; 86 | bufferOffset = (bufferOffset + 1) & BUFFER_MASK; 87 | } 88 | continue; 89 | } 90 | 91 | // end of stream 92 | if (data == 0xFF) 93 | break; 94 | 95 | // block copy 96 | // directly copy group of bytes 97 | /* 98 | input stream: 99 | 00: llll lll0 100 | length: [8 - 69] 101 | directly copy (length+1) bytes 102 | */ 103 | length = data - 0xB9; 104 | //printf("block copy %d bytes\n", length+1); 105 | while (length-- >= 0) 106 | { 107 | data = src[srcOffset++]; 108 | dst[dstOffset++] = data; 109 | buffer[bufferOffset] = data; 110 | bufferOffset = (bufferOffset + 1) & BUFFER_MASK; 111 | } 112 | } 113 | 114 | return dstOffset; 115 | } 116 | -------------------------------------------------------------------------------- /BemaniLZ/BemaniLZ.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef BEMANILZ_H 3 | #define BEMANILZ_H 4 | 5 | #include 6 | 7 | // Decompress data compressed with KonamiLZ 8 | // Return value: final decompressed size 9 | int decompress(uint8_t *src, int srcSize, uint8_t *dst, int dstSize); 10 | 11 | #endif // BEMANILZ_H 12 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 2.6) 2 | 3 | project(tcbtools) 4 | 5 | # 6 | # Libraries 7 | # 8 | file(GLOB sources_pngquant 9 | pngquant/*.c 10 | ) 11 | add_library(pngquant ${sources_pngquant}) 12 | 13 | file(GLOB sources_lodepng 14 | lodepng/*.c 15 | ) 16 | add_library(lodepng ${sources_lodepng}) 17 | 18 | file(GLOB sources_bemanilz 19 | BemaniLZ/*.c 20 | ) 21 | add_library(bemanilz ${sources_bemanilz}) 22 | 23 | # 24 | # Executables 25 | # 26 | file(GLOB sources_tcb_extract 27 | tcb-extract.c 28 | ) 29 | add_executable(tcb-extract ${sources_tcb_extract}) 30 | target_link_libraries(tcb-extract bemanilz) 31 | 32 | file(GLOB sources_tcb_convert 33 | tcb-convert.c 34 | ) 35 | add_executable(tcb-convert ${sources_tcb_convert}) 36 | target_link_libraries(tcb-convert bemanilz pngquant lodepng) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-2018 Wesley Castro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DDR Tools 2 | Tools for extracting and modifying files from Dance Dance Revolution CS games. 3 | 4 | ## TCB Tools 5 | Tools for extracting and converting TCB image files. 6 | 7 | ### Building 8 | ``` 9 | mkdir build 10 | cd build 11 | cmake .. 12 | make 13 | ``` 14 | 15 | ### Usage 16 | 17 | #### tcb-extract 18 | Search for and extract compressed and uncompressed TCB files within a file. 19 | 20 | ``` 21 | ./tcb-extract 22 | ``` 23 | Modes: 24 | * `1` - Bruteforce search for compressed and uncompressed TCBs. 25 | * `2` - Extract TCBs from file beginning with table (compressed entries). 26 | * `3` - Extract TCBs from file beginning with table (uncompressed entries). 27 | 28 | In most cases `1` works best as many table variations exist that `2` and `3` 29 | won't work with. 30 | 31 | #### tcb-convert 32 | Convert TCB files to PNG images or inject PNG images back into TCB files. The 33 | extracted image will be a standard RGBA PNG image converted from either a 16 or 34 | 256 color palletized source. When injecting a PNG back into a TCB, the image 35 | data will be updated and a new pallete will be generated to match the TCB's 36 | original format. The PNG you inject must be the same resolution as the TCB. 37 | 38 | > Convert TCB to PNG: 39 | ``` 40 | ./tcb-convert e 41 | ``` 42 | 43 | > Inject PNG into TCB: 44 | ``` 45 | ./tcb-convert i 46 | ``` 47 | 48 | ## filedata-tool.py 49 | Extract and create filedata.bin files. 50 | 51 | ``` 52 | python3 filedata-tool.py 53 | ``` 54 | Modes: 55 | * `extract` - Extract the contents of `filedata.bin` to `directory`. All files 56 | referenced in the file table located in the game's `elf file` will be 57 | extracted in addition to "hidden" data missing from the file table. A CSV file 58 | will be created named `directory\fieldata.csv` containing IDs, offsets, and 59 | lengths found in the game's ELF, hidden file offsets and lengths, the exported 60 | filename, and a guessed description of the file contents to aid in 61 | modification. 62 | * `create` - Create `filedata.bin` using files in `directory`. The CSV created 63 | by the extraction mode is used to assemble a new file in the correct order and 64 | to update the file table in `elf file`. 65 | 66 | ### Tips 67 | * Don't modify the ids, offsets, or lengths in the CSV file created by the 68 | extraction mode. The filenames can be changed if desired. 69 | * Don't change the order of the rows in the CSV file. It matches the order of 70 | the file table found in the game's ELF. 71 | * New entries can't be added to the filetable, although this wouldn't be useful 72 | anyway. Instead, existing entries can be modified. 73 | -------------------------------------------------------------------------------- /filedata-tool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import struct 4 | import os 5 | from decimal import * 6 | from sys import argv 7 | import sys 8 | import csv 9 | import argparse 10 | 11 | class FileData: 12 | # Length of the ELF is used as the key 13 | table_info = { 14 | 0x176C2C: { 15 | # Tested working 16 | 'name': 'MAX JP', 17 | 'offset': 0x175B18, 18 | 'num_entries': 374 19 | }, 20 | 0x19D568: { 21 | # Tested working 22 | 'name': 'MAX US', 23 | 'offset': 0x168320, 24 | 'num_entries': 577 25 | }, 26 | 0x1DAC88: { 27 | 'name': 'MAX 2 JP', 28 | 'offset': 0x17DDE8, 29 | 'num_entries': 675 30 | }, 31 | 0x265854: { 32 | 'name': 'MAX 2 US', 33 | 'offset': 0x1A0810, 34 | 'num_entries': 795 35 | }, 36 | 0x12A608: { 37 | 'name': 'MAX 2 E3 Demo US', 38 | 'offset': 0x1842F0, 39 | 'num_entries': 223 40 | }, 41 | 2672124: { 42 | # Tested working 43 | 'name': 'Extreme JP', 44 | 'offset': 0x1B3130, 45 | 'num_entries': 656 46 | }, 47 | 3871008: { 48 | 'name': 'Extreme E3 Demo US', 49 | 'offset': 0x17C880, 50 | 'num_entries': 680 51 | }, 52 | 2725576: { 53 | 'name': 'Party Collection JP', 54 | 'offset': 0x1A1548, 55 | 'num_entries': 459 56 | } 57 | } 58 | 59 | csv_fieldnames = ['id', 'offset', 'length', 'filename', 'description'] 60 | 61 | def _get_table_info(self, elf_path): 62 | length = os.path.getsize(elf_path) 63 | try: 64 | return self.table_info[length] 65 | except: 66 | return None 67 | 68 | def _guess_filetype(self, stream, length): 69 | ''' Guess the filetype of a binary stream 70 | 71 | Parameters: 72 | stream: file stream at start of data 73 | length: length of data 74 | 75 | Return value: 76 | Dictionary with description and extension keys. 77 | The file pointer will be returned to its original position. 78 | ''' 79 | 80 | if length > 4 and stream.peek(4)[:4] == b'Svag': 81 | return {'description': 'Konami PS2 SVAG Audio', 'extension': 'svag'} 82 | elif length > 4 and stream.peek(4)[:4] == b'ipum': 83 | return {'description': 'Sony PS2 IPU Video', 'extension': 'ipu'} 84 | elif length > 16 and stream.peek(16)[:16] == b'TCB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00': 85 | return {'description': 'Konami TCB Image', 'extension': 'tcb'} 86 | elif length > 13 and stream.peek(13)[4:13] == b'FrameInfo': 87 | return {'description': 'MPEG2 Stream', 'extension': 'mpeg2'} 88 | else: 89 | return {'description': 'Unknown Binary Data', 'extension': 'bin'} 90 | 91 | def _print_progress(self, iteration, total): 92 | ''' Print progress info to stdout ''' 93 | percent = int(round(100 * iteration/total, 0)) 94 | bar_filled = percent // 10 95 | bar = '#' * bar_filled + '-' * (10 - bar_filled) 96 | sys.stdout.write('\r[{}] {}%'.format(bar, percent)) 97 | sys.stdout.flush() 98 | 99 | def __init__(self, elf_path, filedata_path): 100 | self.elf_path = elf_path 101 | self.filedata_path = filedata_path 102 | self.table = [] 103 | self.table_info = self._get_table_info(elf_path) 104 | if self.table_info == None: 105 | raise LookupError('Unknown ELF; Cannot extract filetable.') 106 | 107 | def load_from_elf(self): 108 | ''' Read table contents from ELF file. ''' 109 | filedata_file = open(self.filedata_path, 'rb') 110 | with open(self.elf_path, 'rb') as elf_file: 111 | elf_file.seek(self.table_info['offset'], os.SEEK_SET) 112 | for _ in range(self.table_info['num_entries']): 113 | data = struct.unpack('