mirror of
https://silica.codes/BedrockReverse/McTools.git
synced 2025-05-22 19:16:25 +12:00
Upload src
This commit is contained in:
parent
df72a81caf
commit
11bc128354
43 changed files with 76263 additions and 9 deletions
73
McCrypt/Crypto.cs
Normal file
73
McCrypt/Crypto.cs
Normal file
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace McCrypt
|
||||
{
|
||||
internal class Crypto
|
||||
{
|
||||
internal static byte[] Sha256(byte[] data)
|
||||
{
|
||||
SHA256 sha256 = SHA256.Create();
|
||||
byte[] hash = sha256.ComputeHash(data);
|
||||
sha256.Dispose();
|
||||
return hash;
|
||||
}
|
||||
internal static byte[] Aes256CfbEncrypt(byte[] key, byte[] iv, byte[] data)
|
||||
{
|
||||
Aes aes = Aes.Create();
|
||||
aes.Mode = CipherMode.CFB;
|
||||
aes.Padding = PaddingMode.None;
|
||||
aes.BlockSize = 128;
|
||||
aes.KeySize = 256;
|
||||
|
||||
ICryptoTransform aesEncryptor = aes.CreateEncryptor(key, iv);
|
||||
using (MemoryStream msEncrypt = new MemoryStream())
|
||||
{
|
||||
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, aesEncryptor, CryptoStreamMode.Write))
|
||||
{
|
||||
csEncrypt.Write(data, 0, data.Length);
|
||||
|
||||
long totalWritten = data.Length;
|
||||
while ((totalWritten % 16 != 0))
|
||||
{
|
||||
csEncrypt.WriteByte(0);
|
||||
totalWritten++;
|
||||
}
|
||||
|
||||
msEncrypt.Seek(0x00, SeekOrigin.Begin);
|
||||
return msEncrypt.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static byte[] Aes256CfbDecrypt(byte[] key, byte[] iv, byte[] data)
|
||||
{
|
||||
Aes aes = Aes.Create();
|
||||
aes.Mode = CipherMode.CFB;
|
||||
aes.Padding = PaddingMode.Zeros;
|
||||
aes.BlockSize = 128;
|
||||
aes.KeySize = 256;
|
||||
|
||||
ICryptoTransform aesDecryptor = aes.CreateDecryptor(key, iv);
|
||||
using (MemoryStream msDecrypt = new MemoryStream())
|
||||
{
|
||||
msDecrypt.Write(data, 0, data.Length);
|
||||
|
||||
while (msDecrypt.Length % 16 != 0)
|
||||
msDecrypt.WriteByte(0);
|
||||
|
||||
msDecrypt.Seek(0x00, SeekOrigin.Begin);
|
||||
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, aesDecryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
byte[] plaintext = new byte[msDecrypt.Length];
|
||||
csDecrypt.Read(plaintext, 0x00, plaintext.Length);
|
||||
|
||||
Array.Copy(plaintext, data, data.Length);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
302
McCrypt/Keys.cs
Normal file
302
McCrypt/Keys.cs
Normal file
|
@ -0,0 +1,302 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace McCrypt
|
||||
{
|
||||
public class Keys
|
||||
{
|
||||
private static Random rng = new Random();
|
||||
public static string KeyDbFile = "";
|
||||
internal struct content
|
||||
{
|
||||
public string FriendlyId;
|
||||
public byte[] ContentKey;
|
||||
}
|
||||
public struct keysJsonStruct
|
||||
{
|
||||
public string id;
|
||||
public string contentKey;
|
||||
}
|
||||
|
||||
public static string lastTitleAccountId = "";
|
||||
public static string lastMinecraftId = "";
|
||||
|
||||
private static string lastDeviceId = "";
|
||||
private static List<content> contentList = new List<content>();
|
||||
public static string ExportKeysJson()
|
||||
{
|
||||
List<keysJsonStruct> keysJson = new List<keysJsonStruct>();
|
||||
foreach (content key in contentList.ToArray())
|
||||
{
|
||||
string ckey = Encoding.UTF8.GetString(key.ContentKey);
|
||||
if (ckey == "s5s5ejuDru4uchuF2drUFuthaspAbepE")
|
||||
continue;
|
||||
|
||||
keysJsonStruct kjs = new keysJsonStruct();
|
||||
kjs.id = key.FriendlyId;
|
||||
kjs.contentKey = ckey;
|
||||
keysJson.Add(kjs);
|
||||
}
|
||||
|
||||
return JsonConvert.SerializeObject(keysJson);
|
||||
}
|
||||
private static byte[] deriveUserKey(string UserId, string DeviceId)
|
||||
{
|
||||
byte[] userBytes = Encoding.Unicode.GetBytes(UserId);
|
||||
byte[] deviceBytes = Encoding.Unicode.GetBytes(DeviceId);
|
||||
|
||||
int kLen = userBytes.Length;
|
||||
if (deviceBytes.Length < kLen)
|
||||
kLen = deviceBytes.Length;
|
||||
|
||||
byte[] key = new byte[kLen];
|
||||
|
||||
for (int i = 0; i < kLen; i++)
|
||||
{
|
||||
key[i] = (byte)(deviceBytes[i] ^ userBytes[i]);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
internal static string GenerateKey()
|
||||
{
|
||||
string allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||
string key = "";
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
key += allowedChars[rng.Next(0, allowedChars.Length)];
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
private static byte[] deriveEntKey(byte[] versionkey, byte[] titleaccountId)
|
||||
{
|
||||
int kLen = versionkey.Length;
|
||||
if (titleaccountId.Length < kLen)
|
||||
kLen = titleaccountId.Length;
|
||||
|
||||
byte[] key = new byte[versionkey.Length];
|
||||
|
||||
for (int i = 0; i < kLen; i++)
|
||||
{
|
||||
key[i] = (byte)(versionkey[i] ^ titleaccountId[i]);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
private static byte[] deriveContentKey(byte[] UserKey, byte[] ContentKey)
|
||||
{
|
||||
int kLen = UserKey.Length;
|
||||
if (ContentKey.Length < kLen)
|
||||
kLen = ContentKey.Length;
|
||||
|
||||
byte[] key = new byte[kLen];
|
||||
|
||||
for (int i = 0; i < kLen; i++)
|
||||
{
|
||||
key[i] = (byte)(UserKey[i] ^ ContentKey[i]);
|
||||
}
|
||||
|
||||
int ckLen = kLen / 2;
|
||||
byte[] contentKey = new byte[ckLen];
|
||||
|
||||
for (int i = 0; i < kLen; i += 2)
|
||||
{
|
||||
contentKey[i / 2] = key[i];
|
||||
}
|
||||
|
||||
return contentKey;
|
||||
}
|
||||
|
||||
public static void AddKey(string FriendlyId, byte[] ContentKey, bool addToKeyCache = true)
|
||||
{
|
||||
if (LookupKey(FriendlyId) != null)
|
||||
return;
|
||||
|
||||
string keyCacheEntry = FriendlyId + "=" + Encoding.UTF8.GetString(ContentKey);
|
||||
|
||||
if (addToKeyCache && KeyDbFile != "")
|
||||
File.AppendAllText(KeyDbFile, keyCacheEntry + "\n");
|
||||
|
||||
content content = new content();
|
||||
content.FriendlyId = FriendlyId;
|
||||
content.ContentKey = ContentKey;
|
||||
|
||||
contentList.Add(content);
|
||||
}
|
||||
|
||||
private static void readReceipt(string receiptData)
|
||||
{
|
||||
dynamic recData = Utils.JsonDecodeCloserToMinecraft(receiptData);
|
||||
string userId = recData.Receipt.EntityId;
|
||||
string deviceId = "";
|
||||
|
||||
if (recData.Receipt.ReceiptData != null)
|
||||
deviceId = recData.Receipt.ReceiptData.DeviceId;
|
||||
|
||||
if (deviceId == "" || deviceId == null)
|
||||
deviceId = lastDeviceId;
|
||||
|
||||
if (deviceId == "" || deviceId == null)
|
||||
return;
|
||||
|
||||
lastDeviceId = deviceId;
|
||||
|
||||
byte[] userKey = deriveUserKey(userId, deviceId);
|
||||
|
||||
// Derive content keys
|
||||
int totalEntitlements = recData.Receipt.Entitlements.Count;
|
||||
|
||||
for (int i = 0; i < totalEntitlements; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
string friendlyId = recData.Receipt.Entitlements[i].FriendlyId;
|
||||
string contentKeyB64 = recData.Receipt.Entitlements[i].ContentKey;
|
||||
if (contentKeyB64 == null)
|
||||
continue;
|
||||
|
||||
byte[] contentKey = Utils.ForceDecodeBase64(contentKeyB64);
|
||||
byte[] realContentKey = deriveContentKey(userKey, contentKey);
|
||||
|
||||
AddKey(friendlyId, realContentKey);
|
||||
|
||||
}
|
||||
catch (Exception) { continue; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void ReadOptionsTxt(string optionsTxtPath)
|
||||
{
|
||||
string[] optionsTxt = File.ReadAllLines(optionsTxtPath);
|
||||
foreach (string option in optionsTxt)
|
||||
{
|
||||
string opt = option.Replace("\r", "").Replace("\n", "").Trim();
|
||||
|
||||
string[] kvpair = opt.Split(':');
|
||||
if (kvpair.Length >= 2)
|
||||
{
|
||||
if (kvpair[0].Trim() == "last_minecraft_id")
|
||||
{
|
||||
lastMinecraftId = kvpair[1].Trim().ToUpper();
|
||||
}
|
||||
|
||||
if (kvpair[0].Trim() == "last_title_account_id")
|
||||
{
|
||||
lastTitleAccountId = kvpair[1].Trim().ToUpper();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string padLastTileAccountId(string lastTitleAccountId)
|
||||
{
|
||||
StringBuilder deriveText = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < 32; i++)
|
||||
deriveText.Append(lastTitleAccountId[i % lastTitleAccountId.Length]);
|
||||
|
||||
return deriveText.ToString();
|
||||
}
|
||||
|
||||
private static string decryptEntitlementFile(string encryptedEnt)
|
||||
{
|
||||
int version = Int32.Parse(encryptedEnt.Substring(7, 1));
|
||||
byte[] versionkey;
|
||||
switch (version)
|
||||
{
|
||||
case 2:
|
||||
default:
|
||||
versionkey = Encoding.UTF8.GetBytes("X(nG*ejm&E8)m+8c;-SkLTjF)*QdN6_Y");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
string deriveText = padLastTileAccountId(lastTitleAccountId);
|
||||
byte[] entKey = deriveEntKey(versionkey, Encoding.UTF8.GetBytes(deriveText));
|
||||
|
||||
string entBase64 = encryptedEnt.Substring(8);
|
||||
byte[] entCiphertext = Utils.ForceDecodeBase64(entBase64);
|
||||
|
||||
byte[] entPlaintext = Marketplace.decryptEntitlementBuffer(entCiphertext, entKey);
|
||||
|
||||
return Encoding.UTF8.GetString(entPlaintext);
|
||||
}
|
||||
public static void ReadEntitlementFile(string entPath)
|
||||
{
|
||||
string jsonData = File.ReadAllText(entPath);
|
||||
|
||||
|
||||
if(jsonData.StartsWith("Version")) // Thanks mojang, this was a fun challange <3
|
||||
{
|
||||
jsonData = decryptEntitlementFile(jsonData);
|
||||
}
|
||||
dynamic entData;
|
||||
try
|
||||
{
|
||||
entData = Utils.JsonDecodeCloserToMinecraft(jsonData);
|
||||
}
|
||||
catch (Exception) { return; }
|
||||
|
||||
string receiptB64 = entData.Receipt;
|
||||
|
||||
if (receiptB64 == null)
|
||||
return;
|
||||
|
||||
if (receiptB64.Split('.').Length <= 1)
|
||||
return;
|
||||
|
||||
string receiptData = Encoding.UTF8.GetString(Utils.ForceDecodeBase64(receiptB64.Split('.')[1]));
|
||||
readReceipt(receiptData);
|
||||
int totalItems = entData.Items.Count;
|
||||
for (int i = 0; i < totalItems; i++)
|
||||
{
|
||||
string b64Data = entData.Items[i].Receipt;
|
||||
|
||||
if (b64Data == null)
|
||||
continue;
|
||||
|
||||
if (b64Data.Split('.').Length <= 1)
|
||||
continue;
|
||||
|
||||
string recept = Encoding.UTF8.GetString(Utils.ForceDecodeBase64(b64Data.Split('.')[1]));
|
||||
readReceipt(recept);
|
||||
}
|
||||
}
|
||||
public static void ReadKeysDb(string keyFile)
|
||||
{
|
||||
KeyDbFile = keyFile;
|
||||
string[] keyList = File.ReadAllLines(keyFile);
|
||||
foreach (string key in keyList)
|
||||
{
|
||||
if (key.Contains('='))
|
||||
{
|
||||
string[] keys = key.Split('=');
|
||||
if (keys.Length >= 2)
|
||||
{
|
||||
string friendlyId = keys[0];
|
||||
byte[] contentKey = Encoding.UTF8.GetBytes(keys[1]);
|
||||
AddKey(friendlyId, contentKey, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static byte[] LookupKey(string FriendlyId)
|
||||
{
|
||||
foreach (content content in contentList)
|
||||
{
|
||||
if (content.FriendlyId == FriendlyId)
|
||||
return content.ContentKey;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
59
McCrypt/LibMcCrypt.csproj
Normal file
59
McCrypt/LibMcCrypt.csproj
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{4BEF6F52-6545-4BB9-8053-50335A1C6789}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>McCrypt</RootNamespace>
|
||||
<AssemblyName>LibMcCrypt</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Crypto.cs" />
|
||||
<Compile Include="Keys.cs" />
|
||||
<Compile Include="Manifest.cs" />
|
||||
<Compile Include="Marketplace.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Utils.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
125
McCrypt/Manifest.cs
Normal file
125
McCrypt/Manifest.cs
Normal file
|
@ -0,0 +1,125 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace McCrypt
|
||||
{
|
||||
public class Manifest
|
||||
{
|
||||
private struct signatureBlock
|
||||
{
|
||||
public string hash;
|
||||
public string path;
|
||||
}
|
||||
|
||||
public static string SignManifestString(string manifestJson, string setPath)
|
||||
{
|
||||
signatureBlock signBlock = new signatureBlock();
|
||||
signBlock.path = setPath;
|
||||
signBlock.hash = Convert.ToBase64String(Crypto.Sha256(Encoding.UTF8.GetBytes(manifestJson)));
|
||||
|
||||
List<signatureBlock> signatureData = new List<signatureBlock>();
|
||||
signatureData.Add(signBlock);
|
||||
|
||||
string signatureJson = JsonConvert.SerializeObject(signatureData);
|
||||
return signatureJson;
|
||||
}
|
||||
|
||||
public static void SignManifest(string basePath)
|
||||
{
|
||||
string manifestPath = Path.Combine(basePath, "manifest.json");
|
||||
|
||||
signatureBlock signBlock = new signatureBlock();
|
||||
signBlock.path = manifestPath.Remove(0, basePath.Length + 1);
|
||||
signBlock.hash = Convert.ToBase64String(Crypto.Sha256(File.ReadAllBytes(manifestPath)));
|
||||
|
||||
List<signatureBlock> signatureData = new List<signatureBlock>();
|
||||
signatureData.Add(signBlock);
|
||||
|
||||
string signatureJson = JsonConvert.SerializeObject(signatureData);
|
||||
string signaturesJsonFile = Path.Combine(basePath, "signatures.json");
|
||||
File.WriteAllText(signaturesJsonFile, signatureJson);
|
||||
}
|
||||
public static string ReadName(string manifestFile)
|
||||
{
|
||||
string defaultName = Path.GetFileName(Path.GetDirectoryName(manifestFile));
|
||||
if (!File.Exists(manifestFile))
|
||||
return Utils.TrimName(defaultName);
|
||||
|
||||
string manifestStr = File.ReadAllText(manifestFile);
|
||||
dynamic manifestData = JsonConvert.DeserializeObject(manifestStr);
|
||||
if (manifestData.header != null)
|
||||
{
|
||||
if (manifestData.header.name != null)
|
||||
{
|
||||
string name = manifestData.header.name;
|
||||
string englishLanguageFile = Path.Combine(Path.GetDirectoryName(manifestFile), "texts", "en_US.lang");
|
||||
|
||||
if (File.Exists(englishLanguageFile))
|
||||
{
|
||||
string[] lines = File.ReadAllLines(englishLanguageFile);
|
||||
foreach (string line in lines)
|
||||
{
|
||||
if (!line.Contains('='))
|
||||
continue;
|
||||
|
||||
string[] values = line.Split('=');
|
||||
|
||||
// How tf does this work??!!
|
||||
|
||||
if (values.Length <= 0)
|
||||
continue;
|
||||
|
||||
if (values[0] == name)
|
||||
return Utils.TrimName(values[1]);
|
||||
|
||||
if (values[0] == "pack.name")
|
||||
return Utils.TrimName(values[1]);
|
||||
|
||||
if (values[0].Contains('.'))
|
||||
{
|
||||
string[] values2 = values[0].Split('.');
|
||||
if (values2.Length <= 0)
|
||||
return Utils.TrimName(defaultName);
|
||||
|
||||
if (values[0].Split('.').Last() == name)
|
||||
return Utils.TrimName(values[1]);
|
||||
|
||||
if (values2[0] == "skinpack")
|
||||
return Utils.TrimName(values[1]);
|
||||
|
||||
}
|
||||
|
||||
if (values[0].Contains(name))
|
||||
return Utils.TrimName(values[1]);
|
||||
}
|
||||
if (name.Contains("."))
|
||||
return Utils.TrimName(defaultName);
|
||||
else
|
||||
return Utils.TrimName(name);
|
||||
}
|
||||
else
|
||||
return Utils.TrimName(defaultName);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return Utils.TrimName(defaultName);
|
||||
}
|
||||
public static string ReadUUID(string manifestPath)
|
||||
{
|
||||
dynamic manifest = JsonConvert.DeserializeObject(File.ReadAllText(manifestPath));
|
||||
return manifest.header.uuid.ToString();
|
||||
}
|
||||
|
||||
public static void ChangeUUID(string manifestPath, string newUUID)
|
||||
{
|
||||
dynamic manifest = JsonConvert.DeserializeObject(File.ReadAllText(manifestPath));
|
||||
manifest.header.uuid = newUUID;
|
||||
File.WriteAllText(manifestPath, JsonConvert.SerializeObject(manifest));
|
||||
}
|
||||
}
|
||||
}
|
329
McCrypt/Marketplace.cs
Normal file
329
McCrypt/Marketplace.cs
Normal file
|
@ -0,0 +1,329 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace McCrypt
|
||||
{
|
||||
public class Marketplace
|
||||
{
|
||||
// Hi mojang <3
|
||||
// https://www.youtube.com/watch?v=jIM6dN3ogbk
|
||||
// https://www.youtube.com/watch?v=mnnYCJNhw7w
|
||||
|
||||
private static string[] dontEncrypt = new string[] { "manifest.json", "contents.json", "texts", "pack_icon.png", "ui"};
|
||||
private struct contentsJson
|
||||
{
|
||||
public int version;
|
||||
public List<Object> content;
|
||||
}
|
||||
private struct contentKeys
|
||||
{
|
||||
public string key;
|
||||
public string path;
|
||||
}
|
||||
|
||||
private struct content
|
||||
{
|
||||
public string path;
|
||||
}
|
||||
|
||||
// Checks if file is inside the filename blacklist
|
||||
private static bool shouldEncrypt(string relPath)
|
||||
{
|
||||
foreach (string part in relPath.Split('/'))
|
||||
if (dontEncrypt.Contains(part))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Removes "prid" NBT tag
|
||||
// This is the one that makes the game tell you to plz buy the pack
|
||||
public static void CrackLevelDat(string levelDatFile)
|
||||
{
|
||||
byte[] find = Encoding.UTF8.GetBytes("prid"); // bytes to locate
|
||||
byte[] leveldat = File.ReadAllBytes(levelDatFile); // read level.dat
|
||||
|
||||
Int64 location = Utils.FindData(leveldat, find); // locate where "prid" is inside level.dat
|
||||
if (location != -1)
|
||||
{
|
||||
FileStream fs = File.Open(levelDatFile, FileMode.Open, FileAccess.ReadWrite); // Open the file for writing
|
||||
fs.Seek(location + 3, SeekOrigin.Begin);
|
||||
fs.WriteByte((byte)'a'); // Change to "pria" which the game will just ignore
|
||||
fs.Close();
|
||||
}
|
||||
}
|
||||
|
||||
// Change all skins type to "free" instead of "paid"
|
||||
// This makes the game let you actually apply them
|
||||
public static void CrackSkinsJson(string skinsJsonFile)
|
||||
{
|
||||
File.WriteAllText(skinsJsonFile, File.ReadAllText(skinsJsonFile).Replace("\"paid\"", "\"free\"")); // Read file, replace all "paid" with "free", write file back
|
||||
}
|
||||
|
||||
// Extract a zipe file to the folder its contained in
|
||||
// And delete the zipe file.
|
||||
public static void CrackZipe(string zipeFile)
|
||||
{
|
||||
ZipFile.ExtractToDirectory(zipeFile, Path.GetDirectoryName(zipeFile));
|
||||
File.Delete(zipeFile);
|
||||
}
|
||||
|
||||
// EncryptContents Overload to generate keys
|
||||
public static string EncryptContents(string basePath, string uuid)
|
||||
{
|
||||
return EncryptContents(basePath, uuid, Keys.GenerateKey());
|
||||
}
|
||||
|
||||
|
||||
// Encrypts a contents.json and all files in it-
|
||||
public static string EncryptContents(string basePath, string uuid, string ContentKey)
|
||||
{
|
||||
string contentsJsonPath = Path.Combine(basePath, "contents.json"); // Path to contents.json
|
||||
|
||||
contentsJson contentsJson = new contentsJson();
|
||||
contentsJson.version = 1;
|
||||
contentsJson.content = new List<object>();
|
||||
|
||||
foreach (string entry in Directory.GetFileSystemEntries(basePath, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
string relPath = entry.Remove(0, basePath.Length + 1); // Get path relative to pack folder
|
||||
relPath = relPath.Replace("\\", "/"); // Replace Windows-Style paths, with UNIX paths
|
||||
|
||||
bool shouldEnc = shouldEncrypt(relPath);
|
||||
|
||||
if (Utils.IsDirectory(entry)) // If its a directroy, add "/" to the end to signify this
|
||||
{
|
||||
relPath += "/";
|
||||
shouldEnc = false;
|
||||
}
|
||||
|
||||
if (shouldEnc) // Check file is not blacklisted file
|
||||
{
|
||||
contentKeys keys = new contentKeys();
|
||||
keys.path = relPath;
|
||||
keys.key = Keys.GenerateKey(); // Generate a random key for this file
|
||||
|
||||
byte[] key = Encoding.UTF8.GetBytes(keys.key); // Copy first 16 bytes of key as IV
|
||||
byte[] iv = new byte[16];
|
||||
Array.Copy(key, iv, 16);
|
||||
|
||||
|
||||
byte[] encryptedData = Crypto.Aes256CfbEncrypt(key, iv, File.ReadAllBytes(entry)); // Encrypt the file
|
||||
File.WriteAllBytes(entry, encryptedData); // Write file
|
||||
|
||||
contentsJson.content.Add(keys); // add to content list
|
||||
}
|
||||
else // Just add to the content list without encrypting it
|
||||
{
|
||||
content content = new content();
|
||||
content.path = relPath;
|
||||
contentsJson.content.Add(content);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
string json = JsonConvert.SerializeObject(contentsJson); // JSON Encode contents.json
|
||||
|
||||
byte[] contentKey = Encoding.UTF8.GetBytes(ContentKey); // Copy first 16 bytes of the key for IV
|
||||
byte[] contentIv = new byte[16];
|
||||
Array.Copy(contentKey, contentIv, 16);
|
||||
|
||||
byte[] encryptedJson = Crypto.Aes256CfbEncrypt(contentKey, contentIv, Encoding.UTF8.GetBytes(json)); // Encrypt JSON
|
||||
|
||||
// Create encrypted file w header
|
||||
FileStream fs = File.OpenWrite(contentsJsonPath);
|
||||
BinaryWriter bw = new BinaryWriter(fs);
|
||||
bw.Write((uint)0);
|
||||
bw.Write((uint)0x9BCFB9FC);
|
||||
bw.Write((UInt64)0);
|
||||
fs.WriteByte((byte)uuid.Length);
|
||||
Utils.WriteString(fs, uuid, 0xEF);
|
||||
fs.Write(encryptedJson, 0, encryptedJson.Length);
|
||||
fs.Close();
|
||||
return ContentKey;
|
||||
}
|
||||
|
||||
// Decrypt encrypted entitlement buffer using a specified ent key
|
||||
internal static byte[] decryptEntitlementBuffer(byte[] EntCiphertext, byte[] EntKey)
|
||||
{
|
||||
byte[] iv = new byte[16];
|
||||
Array.Copy(EntKey, iv, iv.Length);
|
||||
return Crypto.Aes256CfbDecrypt(EntKey, iv, EntCiphertext);
|
||||
}
|
||||
|
||||
// Decrypt a world (leveldb) or contents.json file
|
||||
// For the file types that have a header-
|
||||
private static byte[] worldOrContentsJsonDecrypt(string file)
|
||||
{
|
||||
FileStream fs = File.OpenRead(file); // Open file for reading
|
||||
BinaryReader br = new BinaryReader(fs); // Create a binary reader overlay of it
|
||||
|
||||
if (fs.Length <= 0)
|
||||
{
|
||||
fs.Dispose();
|
||||
return new byte[0] { };
|
||||
}
|
||||
|
||||
uint version = br.ReadUInt32();
|
||||
uint magic = br.ReadUInt32();
|
||||
UInt64 unk = br.ReadUInt64();
|
||||
|
||||
if (version == 0 && magic == 0x9bcfb9fc) // is valid header?
|
||||
{
|
||||
|
||||
int len = fs.ReadByte();
|
||||
string uuid = Utils.ReadString(fs, len); // Read the pack UUID for this file
|
||||
byte[] key = Keys.LookupKey(uuid); // Look for key inside .ent / keys.db
|
||||
|
||||
|
||||
if (key == null)
|
||||
key = Encoding.UTF8.GetBytes("s5s5ejuDru4uchuF2drUFuthaspAbepE"); // Generic skinpack key
|
||||
// Every skinpack has the same key lol
|
||||
// This might be wrong, but hey! if it works, it works :D
|
||||
fs.Seek(0x100, SeekOrigin.Begin); // Read ciphertext
|
||||
byte[] ciphertext = new byte[fs.Length - 0x100];
|
||||
fs.Read(ciphertext, 0x00, ciphertext.Length);
|
||||
|
||||
|
||||
byte[] iv = new byte[16]; // Copy first 16 bytes of Key for IV
|
||||
Array.Copy(key, iv, iv.Length);
|
||||
|
||||
byte[] plaintext = Crypto.Aes256CfbDecrypt(key, iv, ciphertext); // Decrypt data
|
||||
|
||||
fs.Dispose();
|
||||
return plaintext;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
fs.Dispose();
|
||||
throw new InvalidDataException("Not a valid LEVELDB or CONTENTS.JSON file.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Read contents.json, and decrypt all files inside
|
||||
// Now Multi-Threaded for speed!
|
||||
private static void decryptContentsJsonFiles(string contentsJsonPath, List<Thread> threadList)
|
||||
{
|
||||
string baseDirectory = Path.GetDirectoryName(contentsJsonPath); // Get pack folder
|
||||
string contentsJson = File.ReadAllText(contentsJsonPath); // Read contents.json
|
||||
dynamic contentsJsonData = Utils.JsonDecodeCloserToMinecraft(contentsJson); // Parse contents.json
|
||||
|
||||
int totalContents = contentsJsonData.content.Count;
|
||||
for (int i = 0; i < totalContents; i++)
|
||||
{
|
||||
string relPath = contentsJsonData.content[i].path; // Relative path to file to be decrypted
|
||||
string decKey = contentsJsonData.content[i].key; // Key for file to be decrypted
|
||||
if (decKey == null)
|
||||
continue;
|
||||
|
||||
Thread thrd = new Thread(() =>
|
||||
{
|
||||
string filePath = Path.Combine(baseDirectory, relPath); // Combine pack dir, with file relative path
|
||||
byte[] key = Encoding.UTF8.GetBytes(decKey); // Get key bytes
|
||||
byte[] iv = new byte[16];
|
||||
Array.Copy(key, iv, iv.Length); // Copy first 16 bytes of key as IV
|
||||
|
||||
byte[] cipherText = File.ReadAllBytes(filePath); // Read the file
|
||||
byte[] plainText = Crypto.Aes256CfbDecrypt(key, iv, cipherText); // Decrypt the file
|
||||
File.WriteAllBytes(filePath, plainText); // Write back decrypted filie
|
||||
});
|
||||
thrd.Priority = ThreadPriority.Highest;
|
||||
threadList.Add(thrd);
|
||||
thrd.Start();
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt an entire pack / world
|
||||
// Recursively decrypts all sub-packs.
|
||||
// Mutli-Threaded.
|
||||
public static void DecryptContents(string contentsPath)
|
||||
{
|
||||
List<Thread> threadList = new List<Thread>();
|
||||
string oldSchoolZipe = Path.Combine(contentsPath, "content.zipe");
|
||||
string contentsJsonPath = Path.Combine(contentsPath, "contents.json");
|
||||
if (File.Exists(oldSchoolZipe)) // Resource packs or Skin Packs
|
||||
{
|
||||
byte[] decryptedData = worldOrContentsJsonDecrypt(oldSchoolZipe); // Decrypt the zipe file
|
||||
File.WriteAllBytes(oldSchoolZipe, decryptedData); // Write decrypted zip back to disk
|
||||
}
|
||||
else if (File.Exists(contentsJsonPath)) // Resource packs or Skin Packs
|
||||
{
|
||||
string subPacksFolder = Path.Combine(contentsPath, "subpacks");
|
||||
|
||||
byte[] decryptedData = worldOrContentsJsonDecrypt(contentsJsonPath); // Decrypt the contents.json file
|
||||
File.WriteAllBytes(contentsJsonPath, decryptedData); // Write decrypted contents.json back to disk
|
||||
decryptContentsJsonFiles(contentsJsonPath, threadList); // Decrypt all files in contents.json
|
||||
|
||||
// Decrypt all Sub-packs
|
||||
if (Directory.Exists(subPacksFolder))
|
||||
{
|
||||
string[] subPacks = Directory.GetDirectories(subPacksFolder, "*", SearchOption.TopDirectoryOnly);
|
||||
foreach (string subPack in subPacks)
|
||||
DecryptContents(Path.Combine(subPacksFolder, subPack));
|
||||
}
|
||||
}
|
||||
else // World Templates
|
||||
{
|
||||
string behaviourPacksFolder = Path.Combine(contentsPath, "behavior_packs"); // Get World Resource Packs folder
|
||||
string resourcePacksFolder = Path.Combine(contentsPath, "resource_packs"); // Get World Behaviour Packs folder
|
||||
string levelDbFolder = Path.Combine(contentsPath, "db"); // Get leveldb folder
|
||||
|
||||
// Decrypt all sub-behavour packs
|
||||
if (Directory.Exists(behaviourPacksFolder))
|
||||
{
|
||||
string[] behaviourPacks = Directory.GetDirectories(behaviourPacksFolder, "*", SearchOption.TopDirectoryOnly);
|
||||
foreach (string behaviourPack in behaviourPacks)
|
||||
DecryptContents(Path.Combine(behaviourPacksFolder, behaviourPack));
|
||||
}
|
||||
|
||||
// Decrypt all sub-resource packs
|
||||
if (Directory.Exists(resourcePacksFolder))
|
||||
{
|
||||
string[] resourcePacks = Directory.GetDirectories(resourcePacksFolder, "*", SearchOption.TopDirectoryOnly);
|
||||
foreach (string resourcePack in resourcePacks)
|
||||
DecryptContents(Path.Combine(resourcePacksFolder, resourcePack));
|
||||
}
|
||||
|
||||
// Decrypt leveldb files
|
||||
if (Directory.Exists(levelDbFolder))
|
||||
{
|
||||
string[] levelDbFiles = Directory.GetFiles(levelDbFolder, "*", SearchOption.AllDirectories);
|
||||
foreach (string levelDbFile in levelDbFiles)
|
||||
{
|
||||
Thread thrd = new Thread(() =>
|
||||
{
|
||||
string fileToDecrypt = Path.Combine(levelDbFolder, levelDbFile); // Get full path to leveldb file
|
||||
byte[] decryptedData;
|
||||
try
|
||||
{
|
||||
decryptedData = worldOrContentsJsonDecrypt(fileToDecrypt); // Decrypr file
|
||||
File.WriteAllBytes(fileToDecrypt, decryptedData); // Write to disk
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
Console.Error.WriteLine("Failed to decrypt db/" + Path.GetFileName(levelDbFile));
|
||||
}
|
||||
});
|
||||
thrd.Priority = ThreadPriority.Highest;
|
||||
threadList.Add(thrd);
|
||||
thrd.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thread[] threads = threadList.ToArray();
|
||||
threadList.Clear();
|
||||
|
||||
foreach(Thread t in threads)
|
||||
t.Join();
|
||||
}
|
||||
}
|
||||
}
|
36
McCrypt/Properties/AssemblyInfo.cs
Normal file
36
McCrypt/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("McCrypt")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("McCrypt")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2022")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("4bef6f52-6545-4bb9-8053-50335a1c6789")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
97
McCrypt/Utils.cs
Normal file
97
McCrypt/Utils.cs
Normal file
|
@ -0,0 +1,97 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace McCrypt
|
||||
{
|
||||
internal class Utils
|
||||
{
|
||||
|
||||
internal static object JsonDecodeCloserToMinecraft(string json)
|
||||
{
|
||||
for (int i = json.Length; i > 0; i--)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject(json.Substring(0, i));
|
||||
}
|
||||
catch (Exception) { };
|
||||
}
|
||||
throw new Exception();
|
||||
}
|
||||
internal static bool IsDirectory(string path)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
return true;
|
||||
else if (File.Exists(path))
|
||||
return false;
|
||||
else
|
||||
throw new FileNotFoundException("Cannot find file: " + path);
|
||||
}
|
||||
|
||||
internal static Int64 FindData(byte[] data, byte[] pattern)
|
||||
{
|
||||
|
||||
for (Int64 i = 0; i < data.LongLength - pattern.LongLength; i++)
|
||||
{
|
||||
bool match = true;
|
||||
for (Int64 k = 0; k < pattern.LongLength; k++)
|
||||
{
|
||||
if (data[i + k] != pattern[k])
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
internal static string ReadString(Stream str, int len)
|
||||
{
|
||||
byte[] stringBytes = new byte[len];
|
||||
str.Read(stringBytes, 0x00, len);
|
||||
return Encoding.UTF8.GetString(stringBytes);
|
||||
}
|
||||
internal static void WriteString(Stream stream, string str, long totalLength)
|
||||
{
|
||||
byte[] data = Encoding.UTF8.GetBytes(str);
|
||||
long paddingLen = totalLength - data.Length;
|
||||
byte[] padding = new byte[paddingLen];
|
||||
stream.Write(data, 0, data.Length);
|
||||
stream.Write(padding, 0, padding.Length);
|
||||
}
|
||||
|
||||
internal static byte[] ForceDecodeBase64(string base64Data)
|
||||
{
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.FromBase64String(base64Data);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
base64Data += "=";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
internal static string TrimName(string name)
|
||||
{
|
||||
if (name.Contains("#"))
|
||||
{
|
||||
return name.Substring(0, name.IndexOf("#")).Trim();
|
||||
}
|
||||
return name.Trim();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
4
McCrypt/packages.config
Normal file
4
McCrypt/packages.config
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net48" />
|
||||
</packages>
|
Loading…
Add table
Add a link
Reference in a new issue