├── LICENSE ├── README.md └── generateIDTable.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 wukko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ubiart-id-table 2 | UbiArt `idtable.idt` file generator that lets you add new bundles to UbiArt games that use IDTable as way to verify file download completion. More often used for modding Just Dance 2017 on PC. 3 | 4 | ## Why? 5 | `idtable.idt` file contains a list of bundle path hashes. UbiArt games that let you play before download is complete use this file to check file download status. If IDTable file is missing and/or corrupted, there's no way for game to verify consistency of downloaded files and the game will see bundles as "not yet finished downloading". This script lets you overwrite official IDTable to let the game load unofficial bundles. 6 | 7 | ## Supported games 8 | - Just Dance 2017 PC 9 | - Probably Just Dance 2015 - 2022 on PS4 and Xbox One 10 | - ...maybe some other UbiArt games that let you play before download is done 11 | 12 | ## How to use it 13 | This script doesn't depend on any external modules. All you need is Python 3+. 14 | 15 | 1. Download `generateIDTable.py` 16 | 2. Copy downloaded file to desired game directory 17 | 3. Run it by opening command prompt or terminal in directory and running `py generateIDTable.py` 18 | 4. Good job, you have successfully bypassed the annoying bundle limitation! 19 | 20 | You can also use this script as a module (like I usually do). 21 | 22 | ## Customization 23 | You can change default values in the `generateIDTable.py` file to match your modded game. 24 | 25 | ## Credits 26 | UbiArt CRC32 Python implementation by [InvoxiPlayGames](https://gist.github.com/InvoxiPlayGames/4320e6781fa8d17baedd22f6e6ff779c). 27 | -------------------------------------------------------------------------------- /generateIDTable.py: -------------------------------------------------------------------------------- 1 | # 2 | # https://github.com/wukko/ubiart-id-table 3 | # 4 | # UbiArt idtable.idt file generator from existing IPK bundles by https://github.com/wukko 5 | # Tested on PC version of Just Dance 2017. 6 | # This script should work for other UbiArt games that also use IDTable for file list verification, such as Just Dance 2015 - 2022 on PS4 (orbis). 7 | # 8 | # UbiArt CRC32 implementation by https://github.com/InvoxiPlayGames (https://gist.github.com/InvoxiPlayGames/4320e6781fa8d17baedd22f6e6ff779c). 9 | # 10 | # This script includes all matching files in current directory and its subdirectories when used standalone. Keep this in mind when using it. 11 | # 12 | # Credit is required when this script is used in other project to both me (https://github.com/wukko) and https://github.com/InvoxiPlayGames. 13 | 14 | import os 15 | import math 16 | 17 | # Modify these variables for standalone use if needed 18 | out = "idtable.idt" 19 | bext = ".ipk" 20 | gfext = ".gf" 21 | ignore = ["patch"] 22 | p = ["pc", "durango", "scarlett", "orbis", "prospero", "ggp"] 23 | 24 | def shifter(a, b, c): 25 | d = 0 26 | a = (a - b - c) ^ (c >> 0xd) 27 | a = a & 0xffffffff 28 | b = (b - a - c) ^ (a << 0x8) 29 | b = b & 0xffffffff 30 | c = (c - a - b) ^ (b >> 0xd) 31 | c = c & 0xffffffff 32 | a = (a - c - b) ^ (c >> 0xc) 33 | a = a & 0xffffffff 34 | d = (b - a - c) ^ (a << 0x10) 35 | d = d & 0xffffffff 36 | c = (c - a - d) ^ (d >> 0x5) 37 | c = c & 0xffffffff 38 | a = (a - c - d) ^ (c >> 0x3) 39 | a = a & 0xffffffff 40 | b = (d - a - c) ^ (a << 0xa) 41 | b = b & 0xffffffff 42 | c = (c - a - b) ^ (b >> 0xf) 43 | c = c & 0xffffffff 44 | return a, b, c 45 | 46 | def crc(data): 47 | i = 0 48 | a = 0x9E3779B9 49 | b = 0x9E3779B9 50 | c = 0 51 | length = len(data) 52 | 53 | if length > 0xc: 54 | while i < math.floor(length / 0xc): 55 | a += (((((data[i * 0xc + 0x3] << 8) + data[i * 0xc + 0x2]) << 8) + data[i * 0xc + 0x1]) << 8) + data[i * 0xc]; 56 | b += (((((data[i * 0xc + 0x7] << 8) + data[i * 0xc + 0x6]) << 8) + data[i * 0xc + 0x5]) << 8) + data[i * 0xc + 0x4]; 57 | c += (((((data[i * 0xc + 0xb] << 8) + data[i * 0xc + 0xa]) << 8) + data[i * 0xc + 0x9]) << 8) + data[i * 0xc + 0x8]; 58 | i += 1 59 | a, b, c = shifter(a, b, c) 60 | 61 | c += length; 62 | i = length - (length % 0xc); 63 | 64 | decide = (length % 0xc) - 1 65 | if decide >= 0xa: c += data[i + 0xa] << 0x18; 66 | if decide >= 0x9: c += data[i + 0x9] << 0x10; 67 | if decide >= 0x8: c += data[i + 0x8] << 0x8; 68 | if decide >= 0x7: b += data[i + 0x7] << 0x18; 69 | if decide >= 0x6: b += data[i + 0x6] << 0x10; 70 | if decide >= 0x5: b += data[i + 0x5] << 0x8; 71 | if decide >= 0x4: b += data[i + 0x4]; 72 | if decide >= 0x3: a += data[i + 0x3] << 0x18; 73 | if decide >= 0x2: a += data[i + 0x2] << 0x10; 74 | if decide >= 0x1: a += data[i + 0x1] << 0x8; 75 | if decide >= 0x0: a += data[i + 0x0]; 76 | 77 | a, b, c = shifter(a, b, c) 78 | 79 | return c 80 | 81 | def nameOnly(name, ext): 82 | name = name.split('/')[len(name.split('/'))-1] 83 | for i in p: 84 | name = name.replace(ext, '').replace('_'+i, '') 85 | return name 86 | 87 | def generateIDTable(cwd, out, bext, gfext, ignore): 88 | bl = [] 89 | bn = 0 90 | 91 | for root, dir_names, file_names in os.walk(cwd): 92 | for f in file_names: 93 | path = os.path.join(root, f).replace(cwd, '')[1:] 94 | if path[-4:] == bext or path[-3:] == gfext: 95 | bl.append(path.replace('\\', '/')) 96 | 97 | bl = [b for b in bl if not nameOnly(b, bext) in ignore] 98 | 99 | with open(out, "wb") as f: 100 | f.write((len(bl) + 1).to_bytes(4, "big")) # hash counter offset might be wrong, but that's what official idtable usually has (+1) 101 | for i in bl: 102 | f.write(crc(bytearray(i.upper(), "utf8")).to_bytes(4, "big")) 103 | f.write(bn.to_bytes(4, "big")) 104 | bn += 1 105 | 106 | if __name__ == "__main__": 107 | generateIDTable(os.getcwd(), out, bext, gfext, ignore) 108 | --------------------------------------------------------------------------------