use super::class_reader::{
AASTORE, BIPUSH, ClassFile, CodeAttribute, ConstantPool, CpInfo, GETSTATIC, ICONST_0, ICONST_1,
ICONST_2, ICONST_3, ICONST_4, ICONST_5, ICONST_M1, INVOKESPECIAL, LDC, LDC_W, NEW, SIPUSH,
};
use super::{LabelPurpose, LabelQualifier, ParseResult, StreamLabel, StreamLabelType, jar, vocab};
use crate::sector::SectorSource;
use crate::udf::UdfFs;
use std::collections::{HashMap, HashSet};
pub fn detect(udf: &UdfFs) -> bool {
jar::has_any_top_level_jar(udf)
}
pub fn parse(reader: &mut dyn SectorSource, udf: &UdfFs) -> Option<ParseResult> {
jar::for_each_jar(reader, udf, |entry_name, archive| {
if !jar::has_path_prefix(archive, "com/bydeluxe/") {
return None;
}
let enums = identify_master_enums(archive);
if enums.is_empty() {
tracing::info!(
jar = %entry_name,
"deluxe: com/bydeluxe/ present but no master enum fingerprint matched"
);
return None;
}
for (label, m) in &enums {
tracing::info!(
jar = %entry_name,
enum = %label,
class = %m.class_name,
count = m.values.len(),
"deluxe master enum identified",
);
}
let master_table = MasterEnumTable::from(&enums);
let codec_shape = find_codec_enum(archive);
let codec_table = match codec_shape.as_ref() {
Some(shape) => decode_codec_enum(archive, shape),
None => CodecTable::default(),
};
if let Some(shape) = &codec_shape {
tracing::info!(
jar = %entry_name,
class = %shape.class_name,
count = codec_table.codecs.len(),
"deluxe codec enum decoded",
);
}
let binding_classes = find_binding_classes(archive, &master_table.class_name_set());
if binding_classes.is_empty() {
tracing::info!(
jar = %entry_name,
"deluxe: no binding class found (no class has enough getstatic refs to master enums)"
);
return None;
}
for (name, count) in &binding_classes {
tracing::info!(
jar = %entry_name,
binding_class = %name,
getstatic_count = count,
"deluxe binding class candidate",
);
}
let mut streams: Vec<Construction> = Vec::new();
for (name, _) in &binding_classes {
streams.extend(decode_binding(archive, name, &master_table));
}
if streams.is_empty() {
tracing::info!(
jar = %entry_name,
"deluxe: binding classes found but produced 0 decoded streams"
);
return None;
}
let labels = interpret_streams(&streams, &master_table);
if labels.is_empty() {
return None;
}
tracing::info!(
jar = %entry_name,
audio = labels.iter().filter(|l| l.stream_type == StreamLabelType::Audio).count(),
subtitle = labels.iter().filter(|l| l.stream_type == StreamLabelType::Subtitle).count(),
"deluxe emitted labels",
);
Some(ParseResult::medium(labels))
})
}
#[derive(Debug)]
pub(crate) struct MasterEnum {
pub class_name: String,
pub values: Vec<String>,
}
struct Fingerprint {
label: &'static str,
prefix: &'static [&'static str],
expected_count: usize,
}
const FINGERPRINTS: &[Fingerprint] = &[
Fingerprint {
label: "Language",
prefix: &["English", "French", "Spanish", "Dutch"],
expected_count: 70,
},
Fingerprint {
label: "Purpose",
prefix: &["Normal", "Commentary", "PiP", "Trivia"],
expected_count: 8,
},
Fingerprint {
label: "VideoFormat",
prefix: &["HD", "HDR10 Plus", "HD Dolby"],
expected_count: 7,
},
Fingerprint {
label: "Region",
prefix: &["USA_D1", "LIC1", "LIC2", "LIC3"],
expected_count: 22,
},
Fingerprint {
label: "Studio",
prefix: &["Disney", "Marvel", "Pixar"],
expected_count: 6,
},
];
const LDC_COUNT_TOLERANCE: usize = 4;
pub(crate) fn identify_master_enums(archive: &mut jar::Jar) -> Vec<(&'static str, MasterEnum)> {
use std::collections::HashMap;
let mut candidates: HashMap<String, Vec<String>> = HashMap::new();
jar::for_each_class(archive, |class_name, class| {
let Some(ldcs) = clinit_ldc_strings(class) else {
return;
};
if ldcs.is_empty() {
return;
}
candidates.insert(class_name.to_string(), ldcs);
});
let mut out = Vec::new();
for fp in FINGERPRINTS {
let mut best: Option<(String, Vec<String>)> = None;
for (name, ldcs) in &candidates {
if !ldcs_match_prefix(ldcs, fp.prefix) {
continue;
}
let count = ldcs.len();
if count.abs_diff(fp.expected_count) > LDC_COUNT_TOLERANCE {
continue;
}
match &best {
None => best = Some((name.clone(), ldcs.clone())),
Some((_, prev)) => {
if count == fp.expected_count && prev.len() != fp.expected_count {
best = Some((name.clone(), ldcs.clone()));
}
}
}
}
if let Some((class_name, values)) = best {
out.push((fp.label, MasterEnum { class_name, values }));
}
}
out
}
fn clinit_ldc_strings(class: &super::class_reader::ClassFile) -> Option<Vec<String>> {
let mut found = false;
let mut out = Vec::new();
for m in &class.methods {
let Some(name) = class.member_name(m) else {
continue;
};
if name != "<clinit>" {
continue;
}
found = true;
let Some(code) = m.code(&class.constant_pool) else {
continue;
};
for insn in code.instructions() {
if insn.opcode != LDC && insn.opcode != LDC_W {
continue;
}
let Some(idx) = insn.cp_index() else {
continue;
};
let resolved = match class.constant_pool.get(idx) {
Some(CpInfo::String { string_index }) => {
class.constant_pool.utf8(*string_index).map(str::to_string)
}
Some(CpInfo::Utf8(s)) => Some(s.clone()),
_ => None,
};
if let Some(s) = resolved {
out.push(s);
}
}
}
if found { Some(out) } else { None }
}
fn ldcs_match_prefix(ldcs: &[String], prefix: &[&str]) -> bool {
if ldcs.len() < prefix.len() {
return false;
}
ldcs.iter()
.zip(prefix.iter())
.all(|(got, want)| got == want)
}
pub(crate) fn find_codec_enum(archive: &mut jar::Jar) -> Option<CodecEnumShape> {
let mut best: Option<(String, Vec<String>)> = None;
jar::for_each_class(archive, |class_name, class| {
let Some((news, ldcs)) = clinit_news_and_ldcs(class) else {
return;
};
if news.len() < 20 || !ldcs.is_empty() {
return;
}
match &best {
None => best = Some((class_name.to_string(), news)),
Some((_, prev)) => {
if news.len() > prev.len() {
best = Some((class_name.to_string(), news));
}
}
}
});
best.map(|(class_name, subclass_news)| CodecEnumShape {
class_name,
subclass_news,
})
}
#[derive(Debug)]
pub(crate) struct CodecEnumShape {
pub class_name: String,
pub subclass_news: Vec<String>,
}
pub(crate) fn decode_codec_enum(archive: &mut jar::Jar, shape: &CodecEnumShape) -> CodecTable {
let mut name_by_class: HashMap<String, String> = HashMap::new();
let wanted: HashSet<&str> = shape.subclass_news.iter().map(String::as_str).collect();
jar::for_each_class(archive, |class_name, class| {
if !wanted.contains(class_name) {
return;
}
if let Some(name) = extract_codec_name(class) {
name_by_class.insert(class_name.to_string(), name);
}
});
let codecs: Vec<String> = shape
.subclass_news
.iter()
.map(|c| name_by_class.get(c).cloned().unwrap_or_default())
.collect();
CodecTable { codecs }
}
#[derive(Debug, Default, Clone)]
pub(crate) struct CodecTable {
pub codecs: Vec<String>,
}
impl CodecTable {
#[allow(dead_code)] pub fn get(&self, ordinal: u16) -> Option<&str> {
let s = self.codecs.get(ordinal as usize)?;
if s.is_empty() { None } else { Some(s.as_str()) }
}
}
fn extract_codec_name(class: &ClassFile) -> Option<String> {
for (_, entry) in class.constant_pool.iter() {
let CpInfo::Utf8(s) = entry else {
continue;
};
if s.len() < 4 {
continue;
}
if !s
.chars()
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')
{
continue;
}
if !s.contains('_') {
let is_known_root = [
"ATMOS", "DOLBY", "DTS", "TRUEHD", "MLP", "AC3", "EAC3", "PCM",
]
.iter()
.any(|root| s == *root);
if !is_known_root {
continue;
}
}
return Some(s.clone());
}
None
}
#[allow(dead_code)]
fn clinit_news_and_ldcs(
class: &super::class_reader::ClassFile,
) -> Option<(Vec<String>, Vec<String>)> {
let mut news = Vec::new();
let mut ldcs = Vec::new();
let mut found = false;
let mut _aastore = 0u32;
for m in &class.methods {
let Some(name) = class.member_name(m) else {
continue;
};
if name != "<clinit>" {
continue;
}
found = true;
let Some(code) = m.code(&class.constant_pool) else {
continue;
};
for insn in code.instructions() {
match insn.opcode {
NEW => {
if let Some(idx) = insn.cp_index() {
if let Some(n) = class.constant_pool.class_name(idx) {
news.push(n.to_string());
}
}
}
LDC | LDC_W => {
if let Some(idx) = insn.cp_index() {
let s = match class.constant_pool.get(idx) {
Some(CpInfo::String { string_index }) => {
class.constant_pool.utf8(*string_index).map(str::to_string)
}
Some(CpInfo::Utf8(s)) => Some(s.clone()),
_ => None,
};
if let Some(s) = s {
ldcs.push(s);
}
}
}
AASTORE => _aastore += 1,
_ => {}
}
}
}
if found { Some((news, ldcs)) } else { None }
}
pub(crate) fn find_binding_classes(
archive: &mut jar::Jar,
master_enum_classes: &HashSet<&str>,
) -> Vec<(String, usize)> {
const MIN_GETSTATIC: usize = 4;
let mut candidates: Vec<(String, usize)> = Vec::new();
jar::for_each_class(archive, |class_name, class| {
let count = count_master_enum_getstatic(class, master_enum_classes);
if count >= MIN_GETSTATIC {
candidates.push((class_name.to_string(), count));
}
});
candidates.sort_by_key(|(_, c)| std::cmp::Reverse(*c));
if let Some(top_count) = candidates.first().map(|(_, c)| *c) {
let threshold = (top_count * 2) / 5; candidates.retain(|(_, c)| *c >= threshold);
candidates.truncate(4);
}
candidates
}
fn count_master_enum_getstatic(class: &ClassFile, master_enum_classes: &HashSet<&str>) -> usize {
let mut count = 0usize;
for m in &class.methods {
if class.member_name(m) != Some("<clinit>") {
continue;
}
let Some(code) = m.code(&class.constant_pool) else {
continue;
};
for insn in code.instructions() {
if insn.opcode != GETSTATIC {
continue;
}
let Some(idx) = insn.cp_index() else {
continue;
};
let Some(member) = class.constant_pool.member_ref(idx) else {
continue;
};
if master_enum_classes.contains(member.class_name) {
count += 1;
}
}
}
count
}
#[derive(Debug, Clone)]
pub(crate) struct Construction {
pub binding_type: String,
pub args: Vec<StackVal>,
}
#[derive(Debug, Clone)]
pub(crate) enum StackVal {
Int(i32),
EnumRef {
kind: &'static str,
ordinal: u16,
},
CodingType(String),
NewObj(String),
Unknown,
}
const BD_CODING_TYPE_CLASS: &str = "org/bluray/ti/CodingType";
pub(crate) fn decode_binding(
archive: &mut jar::Jar,
binding_class_name: &str,
master: &MasterEnumTable,
) -> Vec<Construction> {
let mut out: Vec<Construction> = Vec::new();
let target_name = binding_class_name.to_string();
jar::for_each_class(archive, |class_name, class| {
if class_name != target_name {
return;
}
out = decode_binding_class(class, master);
});
out
}
pub(crate) fn decode_binding_class(
class: &ClassFile,
master: &MasterEnumTable,
) -> Vec<Construction> {
let mut all = Vec::new();
for m in &class.methods {
if class.member_name(m) != Some("<clinit>") {
continue;
}
let Some(code) = m.code(&class.constant_pool) else {
continue;
};
let mut ctx = BindingDecoder::new(&class.constant_pool, master);
ctx.run(&code);
all.extend(ctx.constructions);
}
all
}
struct BindingDecoder<'a> {
pool: &'a ConstantPool,
master: &'a MasterEnumTable,
stack: Vec<StackVal>,
constructions: Vec<Construction>,
}
impl<'a> BindingDecoder<'a> {
fn new(pool: &'a ConstantPool, master: &'a MasterEnumTable) -> Self {
Self {
pool,
master,
stack: Vec::new(),
constructions: Vec::new(),
}
}
pub(crate) fn run(&mut self, code: &CodeAttribute<'_>) {
for insn in code.instructions() {
self.step(insn);
}
}
fn step(&mut self, insn: super::class_reader::Instruction<'_>) {
match insn.opcode {
ICONST_M1 => self.stack.push(StackVal::Int(-1)),
ICONST_0 => self.stack.push(StackVal::Int(0)),
ICONST_1 => self.stack.push(StackVal::Int(1)),
ICONST_2 => self.stack.push(StackVal::Int(2)),
ICONST_3 => self.stack.push(StackVal::Int(3)),
ICONST_4 => self.stack.push(StackVal::Int(4)),
ICONST_5 => self.stack.push(StackVal::Int(5)),
BIPUSH => {
if let Some(b) = insn.operand_u8() {
self.stack.push(StackVal::Int(b as i8 as i32));
} else {
self.stack.push(StackVal::Unknown);
}
}
SIPUSH => {
if let Some(w) = insn.operand_u16() {
self.stack.push(StackVal::Int(w as i16 as i32));
} else {
self.stack.push(StackVal::Unknown);
}
}
LDC | LDC_W => {
let v = insn
.cp_index()
.and_then(|i| match self.pool.get(i) {
Some(CpInfo::Integer(n)) => Some(StackVal::Int(*n)),
_ => None,
})
.unwrap_or(StackVal::Unknown);
self.stack.push(v);
}
NEW => {
let class_name = insn
.cp_index()
.and_then(|i| self.pool.class_name(i))
.unwrap_or("")
.to_string();
self.stack.push(StackVal::NewObj(class_name));
}
0x59 => {
if let Some(top) = self.stack.last().cloned() {
self.stack.push(top);
}
}
GETSTATIC => {
let val = insn
.cp_index()
.and_then(|i| self.pool.member_ref(i))
.map(|m| {
if m.class_name == BD_CODING_TYPE_CLASS {
StackVal::CodingType(m.name.to_string())
} else if let Some((kind, ord)) = self.master.resolve(m.class_name, m.name)
{
StackVal::EnumRef { kind, ordinal: ord }
} else {
StackVal::Unknown
}
})
.unwrap_or(StackVal::Unknown);
self.stack.push(val);
}
INVOKESPECIAL => {
let Some(idx) = insn.cp_index() else { return };
let Some(member) = self.pool.member_ref(idx) else { return };
let arg_count = parse_method_arg_count(member.descriptor);
if self.stack.len() < arg_count + 1 {
self.stack.clear();
return;
}
let args: Vec<StackVal> = self
.stack
.split_off(self.stack.len() - arg_count);
let receiver = self.stack.pop().unwrap_or(StackVal::Unknown);
if let StackVal::NewObj(name) = receiver {
if name == member.class_name {
self.constructions.push(Construction {
binding_type: name,
args,
});
}
}
}
0xB6 | 0xB8 | 0xB9 => {
let Some(idx) = insn.cp_index() else { return };
let Some(member) = self.pool.member_ref(idx) else { return };
let arg_count = parse_method_arg_count(member.descriptor);
let extra = if insn.opcode == 0xB6 || insn.opcode == 0xB9 { 1 } else { 0 };
let to_pop = arg_count + extra;
if self.stack.len() < to_pop {
self.stack.clear();
} else {
self.stack.truncate(self.stack.len() - to_pop);
}
if !member.descriptor.ends_with(")V") {
self.stack.push(StackVal::Unknown);
}
}
0x57 => {
self.stack.pop();
}
0x58 => {
self.stack.pop();
self.stack.pop();
}
AASTORE => {
for _ in 0..3 {
self.stack.pop();
}
}
0xB3 => {
self.stack.pop();
}
0xB5 => {
self.stack.pop();
self.stack.pop();
}
0xA7 | 0xB1 => {
self.stack.clear();
}
_ => {
}
}
}
}
fn parse_method_arg_count(descriptor: &str) -> usize {
let bytes = descriptor.as_bytes();
let mut i = 1; let mut count = 0;
while i < bytes.len() && bytes[i] != b')' {
match bytes[i] {
b'[' => {
i += 1;
continue;
}
b'L' => {
while i < bytes.len() && bytes[i] != b';' {
i += 1;
}
i += 1; count += 1;
}
b'B' | b'C' | b'D' | b'F' | b'I' | b'J' | b'S' | b'Z' => {
i += 1;
count += 1;
}
_ => {
break;
}
}
}
count
}
pub(crate) struct MasterEnumTable {
by_class: HashMap<String, (&'static str, HashMap<String, u16>)>,
by_kind: HashMap<&'static str, Vec<String>>,
}
impl MasterEnumTable {
pub(crate) fn from(enums: &[(&'static str, MasterEnum)]) -> Self {
let mut by_class = HashMap::new();
let mut by_kind = HashMap::new();
for (kind, m) in enums {
let field_map: HashMap<String, u16> = m
.values
.iter()
.enumerate()
.map(|(i, v)| (v.clone(), i as u16))
.collect();
by_class.insert(m.class_name.clone(), (*kind, field_map));
by_kind.insert(*kind, m.values.clone());
}
MasterEnumTable { by_class, by_kind }
}
pub(crate) fn class_name_set(&self) -> HashSet<&str> {
self.by_class.keys().map(String::as_str).collect()
}
pub(crate) fn resolve(
&self,
class_name: &str,
field_name: &str,
) -> Option<(&'static str, u16)> {
let (kind, fields) = self.by_class.get(class_name)?;
let ordinal = fields.get(field_name).copied()?;
Some((*kind, ordinal))
}
pub(crate) fn value(&self, kind: &str, ordinal: u16) -> Option<&str> {
self.by_kind
.get(kind)?
.get(ordinal as usize)
.map(String::as_str)
}
}
fn interpret_streams(constructions: &[Construction], master: &MasterEnumTable) -> Vec<StreamLabel> {
let mut audio_idx: u16 = 0;
let mut sub_idx: u16 = 0;
let mut out = Vec::new();
for c in constructions {
let mut lang_ord: Option<u16> = None;
let mut purpose_ord: Option<u16> = None;
let mut coding_type: Option<String> = None;
let mut stream_idx_hint: Option<i32> = None;
for arg in &c.args {
match arg {
StackVal::EnumRef { kind, ordinal } => match *kind {
"Language" => lang_ord = lang_ord.or(Some(*ordinal)),
"Purpose" => purpose_ord = purpose_ord.or(Some(*ordinal)),
_ => {}
},
StackVal::CodingType(name) => {
coding_type = coding_type.or_else(|| Some(name.clone()));
}
StackVal::Int(n) => {
stream_idx_hint = stream_idx_hint.or(Some(*n));
}
_ => {}
}
}
let Some(lang_ord) = lang_ord else { continue };
let codec_hint = coding_type
.as_deref()
.map(coding_type_to_codec_hint)
.map(str::to_string)
.unwrap_or_default();
let (stream_type, stream_number) = if coding_type.is_some() {
audio_idx += 1;
(StreamLabelType::Audio, audio_idx)
} else {
sub_idx += 1;
(StreamLabelType::Subtitle, sub_idx)
};
let lang_value = master.value("Language", lang_ord).unwrap_or("").to_string();
let (language, variant) = match vocab::lang(&lang_value) {
Some(li) => (li.code.to_string(), li.variant.to_string()),
None if !lang_value.is_empty() => (lang_value.clone(), String::new()),
None => (String::new(), String::new()),
};
let (purpose, qualifier) = match purpose_ord {
Some(o) => deluxe_purpose_to_label(o),
None => (LabelPurpose::Normal, LabelQualifier::None),
};
if let Some(hint) = stream_idx_hint {
tracing::debug!(
disc_stream_idx = hint,
lang = %language,
binding = %c.binding_type,
"deluxe interpret_streams: disc-authored stream index (not used for stream_number; preserved for diagnostic)"
);
}
out.push(StreamLabel {
stream_number,
stream_type,
language,
name: lang_value,
purpose,
qualifier,
codec_hint,
variant,
});
}
out
}
fn coding_type_to_codec_hint(field: &str) -> &str {
match field {
"DOLBY_LOSSLESS_AUDIO" => "Dolby TrueHD",
"DTS_HD_LOSSLESS_AUDIO" | "DTS_HD_MA_AUDIO" => "DTS-HD Master Audio",
"LPCM_AUDIO" => "LPCM",
"DOLBY_AC3_AUDIO" => "Dolby Digital",
"DOLBY_DIGITAL_PLUS_AUDIO" => "Dolby Digital Plus",
"DOLBY_ATMOS_AUDIO" => "Dolby Atmos",
"DTS_AUDIO" => "DTS",
"DTS_HD_AUDIO" | "DTS_HD_HR_AUDIO" => "DTS-HD HR",
"MPEG1_AUDIO_LAYER2" | "MPEG2_AUDIO_LAYER2" => "MPEG Audio",
"PG_STREAM" | "PRESENTATION_GRAPHICS_STREAM" => "PGS",
_ => field,
}
}
fn deluxe_purpose_to_label(ordinal: u16) -> (LabelPurpose, LabelQualifier) {
match ordinal {
0 => (LabelPurpose::Normal, LabelQualifier::None),
1 => (LabelPurpose::Commentary, LabelQualifier::None),
2 => (LabelPurpose::Normal, LabelQualifier::None), 3 => (LabelPurpose::Normal, LabelQualifier::None), 4 => (LabelPurpose::Descriptive, LabelQualifier::None),
5 => (LabelPurpose::Score, LabelQualifier::None),
6 => (LabelPurpose::Normal, LabelQualifier::None), 7 => (LabelPurpose::Descriptive, LabelQualifier::None), _ => (LabelPurpose::Normal, LabelQualifier::None),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ldcs_match_prefix_exact() {
let ldcs = vec![
"English".to_string(),
"French".to_string(),
"Spanish".to_string(),
];
assert!(ldcs_match_prefix(&ldcs, &["English", "French"]));
assert!(ldcs_match_prefix(&ldcs, &["English", "French", "Spanish"]));
assert!(!ldcs_match_prefix(&ldcs, &["English", "German"]));
assert!(!ldcs_match_prefix(
&ldcs,
&["English", "French", "Spanish", "Dutch"]
));
}
#[test]
fn ldcs_match_prefix_is_case_sensitive() {
let ldcs = vec!["english".to_string(), "french".to_string()];
assert!(!ldcs_match_prefix(&ldcs, &["English", "French"]));
}
#[test]
fn fingerprint_count_tolerance_lock() {
const _: () = assert!(LDC_COUNT_TOLERANCE >= 1 && LDC_COUNT_TOLERANCE <= 10);
}
#[test]
fn fingerprints_cover_documented_enums() {
let labels: Vec<&str> = FINGERPRINTS.iter().map(|fp| fp.label).collect();
assert_eq!(
labels,
vec!["Language", "Purpose", "VideoFormat", "Region", "Studio"]
);
}
#[test]
fn fingerprint_prefixes_nonempty_and_under_expected_count() {
for fp in FINGERPRINTS {
assert!(!fp.prefix.is_empty(), "{} has empty prefix", fp.label);
assert!(
fp.prefix.len() < fp.expected_count,
"{} prefix is not shorter than expected_count",
fp.label
);
}
}
use super::super::class_reader::{ConstantPool, CpInfo};
#[test]
fn parse_method_arg_count_basic_types() {
assert_eq!(parse_method_arg_count("()V"), 0);
assert_eq!(parse_method_arg_count("(I)V"), 1);
assert_eq!(parse_method_arg_count("(II)V"), 2);
assert_eq!(parse_method_arg_count("(IIII)V"), 4);
assert_eq!(parse_method_arg_count("(JD)V"), 2);
assert_eq!(parse_method_arg_count("(BCDFIJSZ)V"), 8);
}
#[test]
fn parse_method_arg_count_reference_types() {
assert_eq!(parse_method_arg_count("(Ljava/lang/String;)V"), 1);
assert_eq!(parse_method_arg_count("(ILjava/lang/String;LFoo;)V"), 3);
assert_eq!(parse_method_arg_count("([I)V"), 1);
assert_eq!(parse_method_arg_count("([[Ljava/lang/Object;)V"), 1);
assert_eq!(
parse_method_arg_count("(I[Ljava/lang/String;Ljava/util/List;)V"),
3
);
}
#[test]
fn parse_method_arg_count_malformed_descriptor() {
assert_eq!(parse_method_arg_count("(Ifoo)V"), 1);
}
fn build_simple_pool() -> ConstantPool {
let entries = vec![
CpInfo::Empty,
CpInfo::Utf8("LanguageEnum".into()),
CpInfo::Class { name_index: 1 },
CpInfo::Utf8("English".into()),
CpInfo::Utf8("LLanguageEnum;".into()),
CpInfo::NameAndType {
name_index: 3,
descriptor_index: 4,
},
CpInfo::Fieldref {
class_index: 2,
name_and_type_index: 5,
},
CpInfo::Utf8("AudioSlot".into()),
CpInfo::Class { name_index: 7 },
CpInfo::Utf8("<init>".into()),
CpInfo::Utf8("(LLanguageEnum;)V".into()),
CpInfo::NameAndType {
name_index: 9,
descriptor_index: 10,
},
CpInfo::Methodref {
class_index: 8,
name_and_type_index: 11,
},
];
ConstantPool::from_entries(entries)
}
fn lang_enum_master() -> MasterEnumTable {
let m = MasterEnum {
class_name: "LanguageEnum".into(),
values: vec!["English".into(), "French".into(), "Spanish".into()],
};
MasterEnumTable::from(&[("Language", m)])
}
#[test]
fn binding_decoder_recognizes_simple_construction() {
let code: Vec<u8> = vec![
NEW,
0,
8, 0x59, GETSTATIC,
0,
6, INVOKESPECIAL,
0,
12, ];
let pool = build_simple_pool();
let master = lang_enum_master();
let attr = super::super::class_reader::CodeAttribute {
max_stack: 4,
max_locals: 0,
code: &code,
};
let mut decoder = BindingDecoder::new(&pool, &master);
decoder.run(&attr);
assert_eq!(decoder.constructions.len(), 1);
let c = &decoder.constructions[0];
assert_eq!(c.binding_type, "AudioSlot");
assert_eq!(c.args.len(), 1);
match &c.args[0] {
StackVal::EnumRef { kind, ordinal } => {
assert_eq!(*kind, "Language");
assert_eq!(*ordinal, 0); }
other => panic!("expected EnumRef, got {:?}", other),
}
}
#[test]
fn binding_decoder_handles_iconst_and_bipush() {
let code: Vec<u8> = vec![
ICONST_1,
NEW,
0,
8,
0x59,
GETSTATIC,
0,
6,
INVOKESPECIAL,
0,
12,
0x57, BIPUSH,
42,
0x57, ];
let pool = build_simple_pool();
let master = lang_enum_master();
let attr = super::super::class_reader::CodeAttribute {
max_stack: 4,
max_locals: 0,
code: &code,
};
let mut decoder = BindingDecoder::new(&pool, &master);
decoder.run(&attr);
assert_eq!(decoder.constructions.len(), 1);
}
#[test]
fn binding_decoder_skips_unmatched_invokespecial() {
let code: Vec<u8> = vec![ICONST_0, GETSTATIC, 0, 6, INVOKESPECIAL, 0, 12];
let pool = build_simple_pool();
let master = lang_enum_master();
let attr = super::super::class_reader::CodeAttribute {
max_stack: 4,
max_locals: 0,
code: &code,
};
let mut decoder = BindingDecoder::new(&pool, &master);
decoder.run(&attr);
assert_eq!(decoder.constructions.len(), 0);
}
#[test]
fn binding_decoder_resolves_master_enum_ordinal() {
let mut entries = vec![
CpInfo::Empty,
CpInfo::Utf8("OtherEnum".into()),
CpInfo::Class { name_index: 1 },
CpInfo::Utf8("FOO".into()),
CpInfo::Utf8("LOtherEnum;".into()),
CpInfo::NameAndType {
name_index: 3,
descriptor_index: 4,
},
CpInfo::Fieldref {
class_index: 2,
name_and_type_index: 5,
},
];
entries.extend(vec![
CpInfo::Utf8("AudioSlot".into()),
CpInfo::Class { name_index: 7 },
CpInfo::Utf8("<init>".into()),
CpInfo::Utf8("(LOtherEnum;)V".into()),
CpInfo::NameAndType {
name_index: 9,
descriptor_index: 10,
},
CpInfo::Methodref {
class_index: 8,
name_and_type_index: 11,
},
]);
let pool = ConstantPool::from_entries(entries);
let master = lang_enum_master(); let code: Vec<u8> = vec![
NEW,
0,
8, 0x59, GETSTATIC,
0,
6, INVOKESPECIAL,
0,
12,
];
let attr = super::super::class_reader::CodeAttribute {
max_stack: 4,
max_locals: 0,
code: &code,
};
let mut decoder = BindingDecoder::new(&pool, &master);
decoder.run(&attr);
assert_eq!(decoder.constructions.len(), 1);
match &decoder.constructions[0].args[0] {
StackVal::Unknown => {}
other => panic!("expected Unknown, got {:?}", other),
}
}
#[test]
fn deluxe_purpose_ordinal_maps_correctly() {
assert_eq!(deluxe_purpose_to_label(0).0, LabelPurpose::Normal);
assert_eq!(deluxe_purpose_to_label(1).0, LabelPurpose::Commentary);
assert_eq!(deluxe_purpose_to_label(4).0, LabelPurpose::Descriptive);
assert_eq!(deluxe_purpose_to_label(5).0, LabelPurpose::Score);
assert_eq!(deluxe_purpose_to_label(7).0, LabelPurpose::Descriptive);
}
#[test]
fn deluxe_purpose_out_of_range_falls_back_to_normal() {
assert_eq!(deluxe_purpose_to_label(99).0, LabelPurpose::Normal);
}
#[test]
fn interpret_streams_emits_subtitle_when_no_codingtype() {
let constructions = vec![Construction {
binding_type: "SubtitleSlot".into(),
args: vec![StackVal::EnumRef {
kind: "Language",
ordinal: 0,
}],
}];
let out = interpret_streams(&constructions, &lang_enum_master());
assert_eq!(out.len(), 1);
assert_eq!(out[0].stream_type, StreamLabelType::Subtitle);
assert_eq!(out[0].language, "eng");
assert_eq!(out[0].codec_hint, "");
}
#[test]
fn interpret_streams_emits_audio_when_codingtype_present() {
let constructions = vec![Construction {
binding_type: "ng".into(),
args: vec![
StackVal::Int(1),
StackVal::EnumRef {
kind: "Language",
ordinal: 0,
},
StackVal::EnumRef {
kind: "Purpose",
ordinal: 0,
},
StackVal::CodingType("DOLBY_LOSSLESS_AUDIO".into()),
],
}];
let out = interpret_streams(&constructions, &lang_enum_master());
assert_eq!(out.len(), 1);
assert_eq!(out[0].stream_type, StreamLabelType::Audio);
assert_eq!(out[0].codec_hint, "Dolby TrueHD");
assert_eq!(out[0].language, "eng");
}
#[test]
fn interpret_streams_purpose_routed_through_deluxe_enum() {
let constructions = vec![Construction {
binding_type: "SubtitleSlot".into(),
args: vec![
StackVal::EnumRef {
kind: "Language",
ordinal: 0,
},
StackVal::EnumRef {
kind: "Purpose",
ordinal: 1, },
],
}];
let out = interpret_streams(&constructions, &lang_enum_master());
assert_eq!(out.len(), 1);
assert_eq!(out[0].purpose, LabelPurpose::Commentary);
}
#[test]
fn interpret_streams_skips_constructions_without_language() {
let constructions = vec![Construction {
binding_type: "SomeOtherType".into(),
args: vec![StackVal::Int(1)],
}];
let out = interpret_streams(&constructions, &lang_enum_master());
assert!(out.is_empty());
}
#[test]
fn coding_type_maps_known_codecs() {
assert_eq!(
coding_type_to_codec_hint("DOLBY_LOSSLESS_AUDIO"),
"Dolby TrueHD"
);
assert_eq!(
coding_type_to_codec_hint("DOLBY_AC3_AUDIO"),
"Dolby Digital"
);
assert_eq!(
coding_type_to_codec_hint("DOLBY_DIGITAL_PLUS_AUDIO"),
"Dolby Digital Plus"
);
assert_eq!(coding_type_to_codec_hint("DTS_AUDIO"), "DTS");
assert_eq!(
coding_type_to_codec_hint("DTS_HD_MA_AUDIO"),
"DTS-HD Master Audio"
);
assert_eq!(coding_type_to_codec_hint("LPCM_AUDIO"), "LPCM");
}
#[test]
fn coding_type_passes_through_unknown() {
assert_eq!(
coding_type_to_codec_hint("FUTURE_CODEC_X"),
"FUTURE_CODEC_X"
);
}
#[test]
fn extract_codec_name_picks_uppercase_with_underscore() {
let candidate_strings = ["Code", "Utf8", "ATMOS_HD_AUDIO", "MyVar"];
let result = candidate_strings.iter().find(|s| {
s.len() >= 4
&& s.chars()
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')
&& s.contains('_')
});
assert_eq!(result, Some(&"ATMOS_HD_AUDIO"));
}
#[test]
fn master_enum_table_resolves_field_to_ordinal() {
let table = lang_enum_master();
assert_eq!(
table.resolve("LanguageEnum", "English"),
Some(("Language", 0))
);
assert_eq!(
table.resolve("LanguageEnum", "French"),
Some(("Language", 1))
);
assert_eq!(
table.resolve("LanguageEnum", "Spanish"),
Some(("Language", 2))
);
assert_eq!(table.resolve("LanguageEnum", "Klingon"), None);
assert_eq!(table.resolve("OtherEnum", "English"), None);
}
#[test]
fn master_enum_table_value_resolves_ordinal_to_string() {
let table = lang_enum_master();
assert_eq!(table.value("Language", 0), Some("English"));
assert_eq!(table.value("Language", 2), Some("Spanish"));
assert_eq!(table.value("Language", 99), None);
assert_eq!(table.value("Unknown", 0), None);
}
#[test]
fn master_enum_table_class_name_set_lists_all_classes() {
let table = lang_enum_master();
let set = table.class_name_set();
assert!(set.contains("LanguageEnum"));
assert_eq!(set.len(), 1);
}
}