227 lines
7.5 KiB
C#
227 lines
7.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
|
|
namespace ffmpeg_decrypt
|
|
{
|
|
public partial class Form1 : Form
|
|
{
|
|
public static string resdir { get; } = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "res");
|
|
|
|
public Form1()
|
|
{
|
|
InitializeComponent();
|
|
|
|
qualityCombo.SelectedIndex = 1; // value == 80
|
|
statuslbl.Text = "";
|
|
decryptRb.Checked = true;
|
|
}
|
|
|
|
private void inpbutton_Click(object sender, EventArgs e)
|
|
{
|
|
using var ofd = new OpenFileDialog { Filter = "Audible Audio Files|*.aax", Title = "Select an Audible Audio File", FileName = "" };
|
|
if (ofd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
inputdisplay.Text = ofd.FileName;
|
|
outputdisplay.Text = Path.GetDirectoryName(ofd.FileName);
|
|
convertbutton.Enabled = true;
|
|
}
|
|
}
|
|
|
|
private void outpbutton_Click(object sender, EventArgs e)
|
|
{
|
|
using var fbd = new FolderBrowserDialog();
|
|
if (fbd.ShowDialog() == DialogResult.OK && !string.IsNullOrWhiteSpace(fbd.SelectedPath))
|
|
outputdisplay.Text = fbd.SelectedPath;
|
|
}
|
|
|
|
private async void convertbutton_Click(object sender, EventArgs e)
|
|
{
|
|
//var sw = Stopwatch.StartNew();
|
|
|
|
// disable UI
|
|
inputPnl.Enabled = false;
|
|
|
|
statuslbl.Text = "Getting File Hash...";
|
|
var checksum = await GetChecksum(inputdisplay.Text);
|
|
|
|
statuslbl.Text = "Cracking Activation Bytes...";
|
|
var activation_bytes = await GetActivationBytes(checksum);
|
|
|
|
statuslbl.Text = "Converting File...";
|
|
var encodeTo
|
|
= decryptRb.Checked ? EncodeTo.DecryptOnly
|
|
: rmp3.Checked ? EncodeTo.Mp3
|
|
: raac.Checked ? EncodeTo.M4b
|
|
: rflac.Checked ? EncodeTo.Flac
|
|
: throw new NotImplementedException();
|
|
await decryptAndSaveFile(activation_bytes, inputdisplay.Text, outputdisplay.Text, txtConsole, encodeTo, int.Parse(qualityCombo.Text));
|
|
|
|
// re-enable UI
|
|
inputPnl.Enabled = true;
|
|
|
|
//sw.Stop();
|
|
//var total = (int)sw.Elapsed.TotalSeconds;
|
|
|
|
statuslbl.Text = "Conversion Complete!";
|
|
}
|
|
|
|
private static async Task<string> GetChecksum(string aaxPath)
|
|
{
|
|
Extract("ffprobe.exe");
|
|
|
|
var startInfo = new ProcessStartInfo
|
|
{
|
|
FileName = Path.Combine(resdir, "ffprobe.exe"),
|
|
Arguments = aaxPath.SurroundWithQuotes(),
|
|
CreateNoWindow = true,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
WorkingDirectory = Directory.GetCurrentDirectory()
|
|
};
|
|
|
|
using var ffp = new Process { StartInfo = startInfo };
|
|
ffp.Start();
|
|
|
|
// checksum is in the debug info. ffprobe's debug info is written to stderr, not stdout
|
|
var ffprobeStderr = ffp.StandardError.ReadToEnd();
|
|
|
|
await Task.Run(() => ffp.WaitForExit());
|
|
|
|
ffp.Close();
|
|
|
|
// example checksum line:
|
|
// ... [aax] file checksum == 0c527840c4f18517157eb0b4f9d6f9317ce60cd1
|
|
var checksum = ffprobeStderr.ExtractString("file checksum == ", 40);
|
|
|
|
return checksum;
|
|
}
|
|
|
|
/// <summary>use checksum to get activation bytes. activation bytes are unique per audible customer. only have to do this 1x/customer</summary>
|
|
private static async Task<string> GetActivationBytes(string checksum)
|
|
{
|
|
Extract("rcrack.exe");
|
|
|
|
Extract("alglib1.dll");
|
|
// RainbowCrack files to recover your own Audible activation data (activation_bytes) in an offline manner
|
|
Extract("audible_byte#4-4_0_10000x789935_0.rtc");
|
|
Extract("audible_byte#4-4_1_10000x791425_0.rtc");
|
|
Extract("audible_byte#4-4_2_10000x790991_0.rtc");
|
|
Extract("audible_byte#4-4_3_10000x792120_0.rtc");
|
|
Extract("audible_byte#4-4_4_10000x790743_0.rtc");
|
|
Extract("audible_byte#4-4_5_10000x790568_0.rtc");
|
|
Extract("audible_byte#4-4_6_10000x791458_0.rtc");
|
|
Extract("audible_byte#4-4_7_10000x791707_0.rtc");
|
|
Extract("audible_byte#4-4_8_10000x790202_0.rtc");
|
|
Extract("audible_byte#4-4_9_10000x791022_0.rtc");
|
|
|
|
var startInfo = new ProcessStartInfo
|
|
{
|
|
FileName = Path.Combine(resdir, "rcrack.exe"),
|
|
Arguments = @". -h " + checksum,
|
|
CreateNoWindow = true,
|
|
RedirectStandardOutput = true,
|
|
UseShellExecute = false,
|
|
WorkingDirectory = Directory.GetCurrentDirectory()
|
|
};
|
|
|
|
using var rcr = new Process { StartInfo = startInfo };
|
|
rcr.Start();
|
|
|
|
var rcrackStdout = rcr.StandardOutput.ReadToEnd();
|
|
|
|
await Task.Run(() => rcr.WaitForExit());
|
|
rcr.Close();
|
|
|
|
// example result
|
|
// 0c527840c4f18517157eb0b4f9d6f9317ce60cd1 \xbd\x89X\x09 hex:bd895809
|
|
var activation_bytes = rcrackStdout.ExtractString("hex:", 8);
|
|
|
|
return activation_bytes;
|
|
}
|
|
|
|
// ProcessStartInfo.Arguments: use Escaper.EscapeArguments instead of .SurroundWithQuotes()
|
|
|
|
// see also: https://stackoverflow.com/questions/4291912/process-start-how-to-get-the-output
|
|
// top 2 answers show: easy, sync, async
|
|
|
|
enum EncodeTo
|
|
{
|
|
/// <summary>Decrypt only. Retain original encoding</summary>
|
|
DecryptOnly,
|
|
/// <summary>LAME MP3</summary>
|
|
Mp3,
|
|
/// <summary>M4B AAC</summary>
|
|
M4b,
|
|
/// <summary>FLAC HD</summary>
|
|
Flac
|
|
}
|
|
|
|
private static async Task decryptAndSaveFile(string activation_bytes, string inputPath, string outputPath, TextBoxBase debugWindow, EncodeTo encodeTo, int encodeQuality = 80)
|
|
{
|
|
Extract("ffmpeg.exe");
|
|
|
|
var fileBase = Path.Combine(outputPath, Path.GetFileNameWithoutExtension(inputPath));
|
|
|
|
string arguments;
|
|
|
|
// only decrypt. no re-encoding
|
|
if (encodeTo == EncodeTo.DecryptOnly)
|
|
{
|
|
var fileout = fileBase + ".m4b";
|
|
arguments = $"-activation_bytes {activation_bytes} -i {inputPath.SurroundWithQuotes()} -vn -c:a copy {fileout.SurroundWithQuotes()}";
|
|
}
|
|
// re-encode. encoding will be determined by file extension
|
|
else // if (convertRb.Checked)
|
|
{
|
|
var fileout = fileBase + "." + encodeTo.ToString().ToLower();
|
|
arguments = $"-y -activation_bytes {activation_bytes} -i {inputPath.SurroundWithQuotes()} -ab {encodeQuality} -map_metadata 0 -id3v2_version 3 -vn {fileout.SurroundWithQuotes()}";
|
|
}
|
|
|
|
// nothing in stdout. progress/debug info is in stderr
|
|
var startInfo = new ProcessStartInfo
|
|
{
|
|
FileName = Path.Combine(resdir, "ffmpeg.exe"),
|
|
Arguments = arguments,
|
|
CreateNoWindow = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
WorkingDirectory = Directory.GetCurrentDirectory()
|
|
};
|
|
|
|
using var ffm = new Process { StartInfo = startInfo, EnableRaisingEvents = true };
|
|
ffm.ErrorDataReceived += (s, ea) => debugWindow.UIThread(() => debugWindow.AppendText($"DEBUG: {ea.Data}\r\n"));
|
|
|
|
ffm.Start();
|
|
ffm.BeginErrorReadLine();
|
|
await Task.Run(() => ffm.WaitForExit());
|
|
ffm.Close();
|
|
}
|
|
|
|
/// <summary>extract embedded resource to file if it doesn't already exist</summary>
|
|
private static void Extract(string resourceName)
|
|
{
|
|
// first determine whether files exist already in res dir
|
|
if (File.Exists(Path.Combine(resdir, resourceName)))
|
|
return;
|
|
|
|
// extract embedded resource
|
|
// this technique works but there are easier ways:
|
|
// https://stackoverflow.com/questions/13031778/how-can-i-extract-a-file-from-an-embedded-resource-and-save-it-to-disk
|
|
Directory.CreateDirectory(resdir);
|
|
using var resource = System.Reflection.Assembly.GetCallingAssembly().GetManifestResourceStream($"{nameof(ffmpeg_decrypt)}.res." + resourceName);
|
|
using var reader = new BinaryReader(resource);
|
|
using var file = new FileStream(Path.Combine(resdir, resourceName), FileMode.OpenOrCreate);
|
|
using var writer = new BinaryWriter(file);
|
|
writer.Write(reader.ReadBytes((int)resource.Length));
|
|
}
|
|
|
|
private void decryptConvertRb_CheckedChanged(object sender, EventArgs e) => convertGb.Enabled = convertRb.Checked;
|
|
}
|
|
}
|