├── misc ├── kagemasa.png └── konamiisfuckinginsane.png ├── nigger.py ├── LICENSE ├── cum.py ├── fuck.cs ├── fuck.py └── README.md /misc/kagemasa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coomsickle92458/konami-mobile-ripping-toolset/HEAD/misc/kagemasa.png -------------------------------------------------------------------------------- /misc/konamiisfuckinginsane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coomsickle92458/konami-mobile-ripping-toolset/HEAD/misc/konamiisfuckinginsane.png -------------------------------------------------------------------------------- /nigger.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | def download(url, path): 5 | file = open(path, "wb") 6 | response = requests.get(url) 7 | for chunk in response.iter_content(chunk_size=1024): 8 | file.write(chunk) 9 | file.close() 10 | 11 | database = json.loads(open("decrypted/ablist.json", "r").read()) 12 | 13 | for asset in database["assetBundles"]: 14 | if asset["bundleName"].startswith("playables/full/music"): 15 | download( 16 | f'http://d3u9qu1rz2zw6v.cloudfront.net/assetbundles/android/1632984632/{asset["publishPath"]}', 17 | f'com.konami.android.jubeat/files/ab/{asset["publishPath"]}' 18 | ) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 kagemasa 2 | 3 | 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: 4 | 5 | The above copyright notice, this permission notice and the word "NIGGER" shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /cum.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import re 3 | import os 4 | import os.path 5 | import json 6 | 7 | def find_metadata(playable_id): 8 | for meta in music_list: 9 | if re.findall(r"(\d+)_bgm", meta["sound"])[0] == playable_id: 10 | return meta 11 | 12 | 13 | music_list = json.loads(open("MusicList-dec.bin", "r").read()) 14 | 15 | for playable_path in glob.glob("./exported/AudioClip/*_bgm.wav"): 16 | 17 | print(playable_path) 18 | 19 | playable_id = re.findall(r"(\d+)_bgm", playable_path)[0] 20 | jacket_path = f'./exported/Texture2D/{playable_id}_jacket.png' 21 | song_folder = f'./jubeat_dump/{playable_id}' 22 | 23 | if not os.path.exists(song_folder): os.mkdir(song_folder) 24 | 25 | os.rename(jacket_path, f'{song_folder}/cover.png') 26 | os.rename(playable_path, f'{song_folder}/{playable_id}.wav') 27 | 28 | for path in glob.glob(f'./jubeat_dump/*'): 29 | 30 | path = path.replace("\\", "/") 31 | 32 | playable_id = re.findall(r"jubeat_dump\/(\d+)", path)[0] 33 | song_folder = f'./jubeat_dump/{playable_id}' 34 | 35 | meta = find_metadata(playable_id) 36 | 37 | os.system(" ".join([ 38 | "ffmpeg", 39 | f'-i "{song_folder}/{playable_id}.wav"', 40 | f'-i "{song_folder}/cover.png"', 41 | f'-map 0:0', 42 | f'-map 1:0', 43 | f'-disposition:v attached_pic', 44 | f'-c:v copy', 45 | f'-id3v2_version 3', 46 | f'-metadata title="{meta["title"]}"', 47 | f'-metadata artist="{meta["artist"]}"', 48 | f'-metadata album="jubeat"', 49 | f'"./jubeat_music/{playable_id}_{meta["title"]}.flac' 50 | ])) 51 | -------------------------------------------------------------------------------- /fuck.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Security.Cryptography; 4 | 5 | class Decrypt 6 | { 7 | private static string filename = "com.konami.android.jubeat/files/Play/MusicList"; 8 | 9 | // These are stored within the global-metadata.dat file. 10 | // Load libil2cpp.so into IDA, load a script to annotate it and look at the JubeatPlus.CacheAssets.cctor method 11 | // sub_D3951C(&Field__PrivateImplementationDetails__5A3388CBB074822B17DC4803B4E6CDB4B1B7D3CF6B838D5B8EA3F7214C816A16); 12 | // sub_D3951C(&Field__PrivateImplementationDetails__936B6263B960F54C42A14CDECEAF2263923582E6606539F03983C22403CC2FB8); 13 | // Look for the two PID initializations, copy the hash at the end and scan for them in dump.cs 14 | // Read appropriate number of bytes from global-metadata.dat (offset is in dump.cs) 15 | 16 | private static byte[] cryptKey = new byte[] { 17 | 0x79, 0xAA, 0x84, 0x26, 0xA3, 0x77, 0xDD, 0x9C, 18 | 0x48, 0xB2, 0x4B, 0x14, 0x6E, 0xB7, 0x66, 0x62 19 | }; 20 | private static byte[] cryptIV = new byte[] { 21 | 0x34, 0x6E, 0x8B, 0x40, 0x9B, 0xFE, 0x9E, 0x4F, 22 | 0xA5, 0xA1, 0xEE, 0xE2, 0x72, 0xBC, 0x72, 0x26 23 | }; 24 | 25 | public static void Main(string[] args) 26 | { 27 | byte[] fileBytes = File.ReadAllBytes(filename); 28 | RijndaelManaged rijndaelManaged = new RijndaelManaged(); 29 | rijndaelManaged.BlockSize = 128; 30 | rijndaelManaged.KeySize = 128; 31 | rijndaelManaged.Mode = CipherMode.CBC; 32 | rijndaelManaged.Padding = PaddingMode.PKCS7; 33 | rijndaelManaged.IV = cryptIV; 34 | rijndaelManaged.Key = cryptKey; 35 | ICryptoTransform decryptor = rijndaelManaged.CreateDecryptor(cryptKey, cryptIV); 36 | byte[] output = decryptor.TransformFinalBlock(fileBytes, 0, fileBytes.Length); 37 | File.WriteAllBytes(filename + "-dec.bin", output); 38 | } 39 | } -------------------------------------------------------------------------------- /fuck.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import os.path 4 | import glob 5 | import json 6 | import struct 7 | import Crypto 8 | import Crypto.Cipher 9 | import Crypto.Cipher.AES 10 | import Crypto.Util 11 | import Crypto.Util.Counter 12 | import Crypto.Util.Padding 13 | import Crypto.Protocol.KDF 14 | import Crypto.Hash 15 | 16 | # Implementation without salt length restriction 17 | def csharp_PBKDF1(password, salt): 18 | hashAlgo = Crypto.Hash.SHA1 19 | password = Crypto.Util.py3compat.tobytes(password) 20 | pHash = hashAlgo.new(password+salt) 21 | digest = pHash.digest_size 22 | for i in Crypto.Util.py3compat.iter_range(100-1): 23 | pHash = pHash.new(pHash.digest()) 24 | return pHash.digest()[:16] 25 | 26 | def decrypt_file(path, path_local): 27 | path = path.replace("\\", "/") 28 | 29 | password = salt = path_local.encode("utf8") 30 | 31 | print(path_local) 32 | 33 | if len(salt) < 8: 34 | salt = salt + salt + b"\0" 35 | 36 | key = csharp_PBKDF1(password, salt) # C# PasswordDeriveBytes.InterationCount = 100 37 | file_size = os.path.getsize(f'./{path}') 38 | 39 | with open(path, "rb") as file: 40 | 41 | decrypter = Crypto.Cipher.AES.new( 42 | key, 43 | Crypto.Cipher.AES.MODE_ECB, 44 | ) 45 | 46 | path_escaped = path_local.replace("/", "_").replace("\\", "_") 47 | 48 | file_output = open(f'decrypted/{path_escaped}', "wb") 49 | 50 | while file_size - file.tell() > 0: 51 | 52 | pos = file.tell() 53 | block_number = int(pos / 16 + 1) 54 | block = bytearray(file.read(16)) 55 | mask = bytearray(decrypter.encrypt(struct.pack("