#![forbid(unsafe_code)]
use alloc::collections::BTreeSet;
use alloc::collections::VecDeque;
use super::header::DW_NUM_CHANNELS;
use super::tables::PeriodTable;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct DwFeatures {
pub enable_delay_counter: bool,
pub enable_delay_multiply: bool,
pub enable_square_waveform: bool,
pub enable_sample_transpose: bool,
pub enable_channel_transpose: bool,
pub enable_volume_fade: bool,
pub enable_half_volume: bool,
pub enable_vibrato: bool,
pub enable_arpeggio: bool,
pub enable_envelopes: bool,
pub note_repeat_is_tie: bool,
}
#[derive(Debug, Clone)]
pub struct DwLayout {
pub variant: DwVariant,
pub period_table: PeriodTable,
pub init_offset: usize,
pub start_offset: isize,
pub play_offset: Option<usize>,
pub sample_info_offset: Option<usize>,
pub sample_data_offset: Option<usize>,
pub sub_song_list_offset: Option<usize>,
pub number_of_samples: Option<u32>,
pub features: DwFeatures,
pub dispatcher: DwDispatcher,
pub command_map: Option<super::command_map::DwCommandMap>,
pub volume_envelope_table_offset: Option<usize>,
pub volume_envelope_table_len: Option<u8>,
pub uses_32bit_pointers: bool,
pub sub_song_row_width: usize,
pub sub_song_header: usize,
pub arpeggio_table_offset: Option<usize>,
pub arpeggio_table_len: Option<u8>,
pub volume_bracket_is_pitch: bool,
pub channel_volume_offset: Option<usize>,
pub master_volume_offset: Option<usize>,
pub period_via_finetune: bool,
pub instrument_volume_offset: Option<usize>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DwDispatcher {
pub thresholds: alloc::vec::Vec<u8>,
}
impl DwDispatcher {
#[inline]
pub fn sample_threshold(&self) -> Option<u8> {
self.thresholds.first().copied()
}
#[inline]
pub fn volume_envelope_threshold(&self) -> Option<u8> {
self.thresholds.get(1).copied()
}
#[inline]
pub fn pitch_arpeggio_threshold(&self) -> Option<u8> {
self.thresholds.get(2).copied()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DwVariant {
Old,
New,
}
const SC68_MAGIC: [u8; 4] = [b'S', b'C', b'6', b'8'];
pub fn detect(payload: &[u8]) -> Option<DwLayout> {
if payload.len() < 0x40 {
return None;
}
if payload[0..4] == SC68_MAGIC {
return None;
}
const INIT_SCAN_LIMIT: usize = 0x1000;
let init_anchor = find_lea_a3_pc_neg(payload, 0, INIT_SCAN_LIMIT)?;
let init_offset = init_anchor;
let start_offset = {
let disp =
i16::from_be_bytes([payload[init_offset + 2], payload[init_offset + 3]]) as isize;
(init_offset as isize + 2) + disp
};
let sub_song_row_width = find_sub_song_row_width(payload).unwrap_or(10);
let uses_32bit_pointers = sub_song_row_width == 18;
let sub_song_header = if sub_song_row_width == 8 {
find_sub_song_header(payload).unwrap_or(0)
} else {
2
};
let variant = if uses_32bit_pointers || matches_at(payload, init_offset + 4, &[0x4A, 0x2B]) {
DwVariant::Old
} else {
DwVariant::New
};
let (period_table, period_via_finetune) = match variant {
DwVariant::New => (
find_period_table_via_lea(payload)
.or_else(|| guess_new_period_table(payload))
.unwrap_or(PeriodTable::P2),
true,
),
DwVariant::Old => match find_period_table_via_lea(payload) {
Some(table) => (table, true),
None => (PeriodTable::P1, false),
},
};
let (sample_data_offset, sample_info_offset, number_of_samples) = {
let pc = walk_for_sample_loader(payload, init_offset);
if pc.0.is_some() {
pc
} else {
let a3 = walk_for_sample_loader_a3(payload, init_offset);
if a3.0.is_some() {
a3
} else {
let flat_pc = flat_scan_sample_loader(payload, false);
if flat_pc.0.is_some() {
flat_pc
} else {
flat_scan_sample_loader(payload, true)
}
}
}
};
let sub_song_list_offset = walk_for_sub_song_table(payload, init_offset);
let play_scan_end = 0x2000.min(payload.len());
let play_offset = find_play_anchor(payload, 0, play_scan_end);
let mut features = match play_offset {
Some(p) => detect_features(payload, p),
None => DwFeatures::default(),
};
features.enable_half_volume = find_effect_jump_table(payload, start_offset)
.map(|table| effect_handler_is_half_volume(payload, table, start_offset))
.unwrap_or(false);
let (dispatcher, bracket_offsets) =
find_dispatcher_cascade_with_offsets(payload).unwrap_or_default();
let (volume_envelope_table_offset, volume_envelope_table_len) = match (
bracket_offsets.get(1),
dispatcher.thresholds.first(),
dispatcher.thresholds.get(1),
) {
(Some(&bracket_addr), Some(&top), Some(&bottom)) => {
let table = read_lea_a2_at(payload, bracket_addr + 12);
let len = top.saturating_sub(bottom);
(table, Some(len))
}
_ => (None, None),
};
let (arpeggio_table_offset, arpeggio_table_len) = match (
bracket_offsets.get(2),
dispatcher.thresholds.get(1),
dispatcher.thresholds.get(2),
) {
(Some(&bracket_addr), Some(&top), Some(&bottom)) => {
let table = find_lea_an_pc_in(payload, bracket_addr, bracket_addr + 0x18);
let len = top.saturating_sub(bottom);
(table, Some(len))
}
_ => (None, None),
};
let bracket1_is_arp = bracket_offsets.get(2).is_none()
&& volume_envelope_table_offset
.zip(volume_envelope_table_len)
.map(|(off, len)| table_looks_like_arpeggio(payload, off, len))
.unwrap_or(false);
let (
volume_envelope_table_offset,
volume_envelope_table_len,
arpeggio_table_offset,
arpeggio_table_len,
volume_bracket_is_pitch,
) = if bracket1_is_arp {
(
None,
None,
volume_envelope_table_offset,
volume_envelope_table_len,
true,
)
} else {
(
volume_envelope_table_offset,
volume_envelope_table_len,
arpeggio_table_offset,
arpeggio_table_len,
false,
)
};
let channel_volume_offset = if matches!(variant, DwVariant::Old) {
find_channel_volume_table(payload, start_offset)
} else {
None
};
let master_volume_offset = find_master_volume_offset(payload);
let instrument_volume_offset = find_instrument_volume_table(payload);
let command_map = super::command_map::detect_command_map(payload, start_offset);
let features = {
let mut f = features;
if command_map.is_some() {
f.enable_channel_transpose = true;
}
f
};
Some(DwLayout {
variant,
period_table,
init_offset,
start_offset,
play_offset,
sample_info_offset,
sample_data_offset,
sub_song_list_offset,
number_of_samples,
features,
dispatcher,
command_map,
volume_envelope_table_offset,
volume_envelope_table_len,
uses_32bit_pointers,
sub_song_row_width,
sub_song_header,
arpeggio_table_offset,
arpeggio_table_len,
volume_bracket_is_pitch,
channel_volume_offset,
master_volume_offset,
period_via_finetune,
instrument_volume_offset,
})
}
fn find_channel_volume_table(payload: &[u8], start_offset: isize) -> Option<usize> {
let mut i = 0usize;
while i + 6 <= payload.len() {
if payload[i] == 0x41
&& payload[i + 1] == 0xEB
&& payload[i + 4] == 0xE3
&& payload[i + 5] == 0x4F
{
let disp = i16::from_be_bytes([payload[i + 2], payload[i + 3]]) as isize;
let off = start_offset + disp;
if off >= 0 && (off as usize) + 2 * DW_NUM_CHANNELS <= payload.len() {
return Some(off as usize);
}
}
i += 2;
}
None
}
fn find_master_volume_offset(payload: &[u8]) -> Option<usize> {
let mut i = 0usize;
while i + 8 <= payload.len() {
if payload[i] == 0x34
&& payload[i + 1] == 0x3A
&& payload[i + 4] == 0xC2
&& payload[i + 5] == 0xC2
&& payload[i + 6] == 0xEC
&& payload[i + 7] == 0x49
{
let disp = i16::from_be_bytes([payload[i + 2], payload[i + 3]]) as isize;
let off = i as isize + 2 + disp;
if off >= 0 {
let off = off as usize;
if let Some(bytes) = payload.get(off..off + 2) {
let master = u16::from_be_bytes([bytes[0], bytes[1]]);
if (1..=64).contains(&master) {
return Some(off);
}
}
}
}
i += 2;
}
None
}
fn table_looks_like_arpeggio(payload: &[u8], table_off: usize, n: u8) -> bool {
const PROBE_ENTRIES: usize = 8;
const MAX_STEPS: usize = 32;
const ARP_MAX: u8 = 0x18;
let entries = (n as usize).min(PROBE_ENTRIES);
if entries == 0 {
return false;
}
let mut saw_terminator = false;
for idx in 0..entries {
let entry_off = table_off + idx * 2;
if entry_off + 2 > payload.len() {
return false;
}
let stream = u16::from_be_bytes([payload[entry_off], payload[entry_off + 1]]) as usize;
if stream == 0 || stream >= payload.len() {
continue;
}
let mut cursor = stream;
let mut steps = 0;
while cursor < payload.len() && steps < MAX_STEPS {
let b = payload[cursor];
if (b & 0x7F) > ARP_MAX {
return false;
}
cursor += 1;
steps += 1;
if b & 0x80 != 0 {
saw_terminator = true;
break;
}
}
}
saw_terminator
}
fn find_lea_an_pc_in(payload: &[u8], start: usize, end: usize) -> Option<usize> {
let end = end.min(payload.len().saturating_sub(3));
let mut i = start;
while i + 3 < end {
let hi = payload[i];
if (hi & 0xF1) == 0x41 && payload[i + 1] == 0xFA {
let disp = i16::from_be_bytes([payload[i + 2], payload[i + 3]]) as i32;
let target = i as i32 + 2 + disp;
if target >= 0 && (target as usize) < payload.len() {
return Some(target as usize);
}
}
i += 2;
}
None
}
fn find_sub_song_row_width(payload: &[u8]) -> Option<usize> {
let end = 0x1000.min(payload.len().saturating_sub(3));
let mut i = 0;
while i + 3 < end {
if payload[i] == 0xC0 && payload[i + 1] == 0xFC && payload[i + 2] == 0x00 {
let imm = payload[i + 3];
if imm == 0x08 || imm == 0x0A || imm == 0x12 {
return Some(imm as usize);
}
}
i += 2;
}
None
}
fn find_sub_song_header(payload: &[u8]) -> Option<usize> {
let end = 0x1000.min(payload.len().saturating_sub(3));
let mut i = 0;
while i + 3 < end {
if payload[i] == 0xC0
&& payload[i + 1] == 0xFC
&& payload[i + 2] == 0x00
&& payload[i + 3] == 0x08
{
let scan_end = (i + 0x80).min(payload.len().saturating_sub(3));
let mut k = i + 4;
while k + 3 < scan_end {
if payload[k] == 0x30
&& payload[k + 1] == 0x70
&& payload[k + 2] == 0x00
&& matches!(payload[k + 3], 0x00 | 0x02)
{
return Some(payload[k + 3] as usize);
}
k += 2;
}
}
i += 2;
}
None
}
fn read_lea_a2_at(payload: &[u8], at: usize) -> Option<usize> {
if at + 3 >= payload.len() {
return None;
}
if payload[at] != 0x45 || payload[at + 1] != 0xFA {
return None;
}
let disp = i16::from_be_bytes([payload[at + 2], payload[at + 3]]) as i32;
let target = (at as i32 + 2 + disp) as i64;
if target < 0 || (target as usize) >= payload.len() {
None
} else {
Some(target as usize)
}
}
fn find_dispatcher_cascade_with_offsets(
payload: &[u8],
) -> Option<(DwDispatcher, alloc::vec::Vec<usize>)> {
chase_cascade(payload, is_dispatcher_bracket)
.or_else(|| chase_cascade(payload, is_dispatcher_bracket_relaxed))
}
fn chase_cascade(
payload: &[u8],
is_bracket: fn(&[u8], usize) -> bool,
) -> Option<(DwDispatcher, alloc::vec::Vec<usize>)> {
let scan_end = 0x2000.min(payload.len());
let mut cursor = 0usize;
let mut entry_addr = None;
while cursor + 9 < scan_end {
if is_bracket(payload, cursor) {
entry_addr = Some(cursor);
break;
}
cursor += 2;
}
let mut next = entry_addr?;
let mut thresholds = alloc::vec::Vec::with_capacity(3);
let mut offsets = alloc::vec::Vec::with_capacity(3);
for _ in 0..3 {
if !is_bracket(payload, next) {
break;
}
offsets.push(next);
let threshold = payload[next + 3];
thresholds.push(threshold);
let disp = payload[next + 5] as i8 as i32;
if disp == 0 {
break;
}
let target = (next as i32 + 6 + disp) as usize;
if target >= scan_end {
break;
}
next = target;
}
if thresholds.is_empty() {
None
} else {
Some((DwDispatcher { thresholds }, offsets))
}
}
fn is_dispatcher_bracket(payload: &[u8], at: usize) -> bool {
if at + 9 >= payload.len() {
return false;
}
payload[at] == 0xB0
&& payload[at + 1] == 0x3C
&& payload[at + 2] == 0x00
&& payload[at + 4] == 0x6D
&& payload[at + 6] == 0x04
&& payload[at + 7] == 0x00
&& payload[at + 8] == 0x00
&& payload[at + 9] == payload[at + 3]
}
fn is_dispatcher_bracket_relaxed(payload: &[u8], at: usize) -> bool {
if at + 5 >= payload.len() {
return false;
}
payload[at] == 0xB0
&& payload[at + 1] == 0x3C
&& payload[at + 2] == 0x00
&& (0x90..=0xD8).contains(&payload[at + 3])
&& (payload[at + 4] == 0x6D || payload[at + 4] == 0x65)
}
fn matches_at(payload: &[u8], at: usize, needle: &[u8]) -> bool {
payload
.get(at..at + needle.len())
.is_some_and(|w| w == needle)
}
fn find_effect_jump_table(payload: &[u8], start_offset: isize) -> Option<usize> {
let scan_end = 0x2000.min(payload.len().saturating_sub(2));
let mut i = 0usize;
while i < scan_end {
if payload[i] == 0x4E && (payload[i + 1] == 0xD2 || payload[i + 1] == 0xF3) {
for back in [8usize, 10] {
if i < back {
continue;
}
let lea = i - back;
if payload[lea + 1] == 0xFA && payload[lea] == 0x45 {
let disp = i16::from_be_bytes([payload[lea + 2], payload[lea + 3]]) as isize;
let table = (lea as isize + 2) + disp;
if table >= 0 && (table as usize) < payload.len() {
return Some(table as usize);
}
}
if payload[lea + 1] == 0xEB && payload[lea] == 0x45 {
let disp = i16::from_be_bytes([payload[lea + 2], payload[lea + 3]]) as isize;
let table = start_offset + disp;
if table >= 0 && (table as usize) < payload.len() {
return Some(table as usize);
}
}
}
}
i += 2;
}
None
}
fn effect_handler_is_half_volume(payload: &[u8], table: usize, start_offset: isize) -> bool {
let handler = |n: usize| -> Option<usize> {
let e = table + n * 2;
if e + 2 > payload.len() {
return None;
}
let v = u16::from_be_bytes([payload[e], payload[e + 1]]) as isize;
let f = start_offset + v;
(f >= 0 && (f as usize) + 2 <= payload.len()).then_some(f as usize)
};
match (handler(8), handler(9)) {
(Some(h8), Some(h9)) => {
payload[h8] == 0x50
&& payload[h8 + 1] == 0xE8
&& payload[h9] == 0x51
&& payload[h9 + 1] == 0xE8
}
_ => false,
}
}
fn find_lea_a3_pc_neg(payload: &[u8], start: usize, end: usize) -> Option<usize> {
let end = end.min(payload.len().saturating_sub(3));
let mut i = start;
while i < end {
if payload[i] == 0x47 && payload[i + 1] == 0xFA && payload[i + 2] >= 0xF0 {
return Some(i);
}
i += 2;
}
None
}
fn walk_call_graph<F>(payload: &[u8], entry: usize, depth_limit: usize, probe: F) -> Option<u32>
where
F: Fn(&[u8], usize, usize) -> Option<u32>,
{
const BODY_LIMIT: usize = 0x400;
let mut visited: BTreeSet<usize> = BTreeSet::new();
let mut queue: VecDeque<(usize, usize)> = VecDeque::new();
queue.push_back((entry, 0));
while let Some((start, depth)) = queue.pop_front() {
if !visited.insert(start) {
continue;
}
let body_end = (start + BODY_LIMIT).min(payload.len());
if let Some(v) = probe(payload, start, body_end) {
return Some(v);
}
if depth >= depth_limit {
continue;
}
let mut i = start;
while i + 1 < body_end {
if payload[i] == 0x4E && payload[i + 1] == 0x75 {
break;
}
if payload[i] == 0x61 && payload[i + 1] == 0x00 && i + 4 <= body_end {
let disp = i16::from_be_bytes([payload[i + 2], payload[i + 3]]) as i32;
let target = i as i32 + 2 + disp;
if target >= 0 && (target as usize) < payload.len() {
queue.push_back((target as usize, depth + 1));
}
i += 4;
continue;
}
i += 2;
}
}
None
}
fn walk_for_sub_song_table(payload: &[u8], entry: usize) -> Option<usize> {
if let Some(off) = walk_call_graph(payload, entry, 3, |p, s, e| {
find_sub_song_lea(p, s, e).map(|v| v as u32)
}) {
return Some(off as usize);
}
walk_call_graph(payload, entry, 3, |p, s, e| {
find_sub_song_lea_fallback(p, s, e).map(|v| v as u32)
})
.map(|v| v as usize)
}
fn find_sub_song_lea_fallback(payload: &[u8], start: usize, end: usize) -> Option<usize> {
let end_eff = end.min(payload.len().saturating_sub(3));
let mut i = start;
while i + 3 < end_eff {
if payload[i] == 0x41 && payload[i + 1] == 0xFA {
let disp = i16::from_be_bytes([payload[i + 2], payload[i + 3]]) as i32;
if disp > 0 {
let target = i as i32 + 2 + disp;
if target >= 0 && (target as usize) + 10 <= payload.len() {
let t = target as usize;
if looks_like_sub_song_row(payload, t) {
return Some(t);
}
}
}
}
i += 2;
}
None
}
fn looks_like_sub_song_row(payload: &[u8], at: usize) -> bool {
if at + 10 > payload.len() {
return false;
}
let speed = payload[at];
if speed == 0 || speed > 0x40 {
return false;
}
for i in 0..4 {
let p = at + 2 + 2 * i;
let off = u16::from_be_bytes([payload[p], payload[p + 1]]) as usize;
if off == 0 || off >= payload.len() {
return false;
}
}
true
}
fn find_sub_song_lea(payload: &[u8], start: usize, end: usize) -> Option<usize> {
let end_eff = end.min(payload.len().saturating_sub(7));
let mut i = start;
while i + 8 < end_eff {
if payload[i] == 0xC0
&& payload[i + 1] == 0xFC
&& payload[i + 2] == 0x00
&& matches!(payload[i + 3], 0x08 | 0x0A | 0x12)
{
let scan_end = (i + 12).min(end_eff);
let mut k = i + 4;
while k + 3 < scan_end {
if payload[k] == 0x41 && payload[k + 1] == 0xFA {
let disp = i16::from_be_bytes([payload[k + 2], payload[k + 3]]) as i32;
let target = k as i32 + 2 + disp;
if target >= 0 && (target as usize) < payload.len() {
return Some(target as usize);
}
}
if payload[k] == 0x41 && payload[k + 1] == 0xEB {
let disp = u16::from_be_bytes([payload[k + 2], payload[k + 3]]) as usize;
if disp < payload.len() {
return Some(disp);
}
}
k += 2;
}
}
i += 2;
}
None
}
fn walk_for_sample_loader_a3(
payload: &[u8],
entry: usize,
) -> (Option<usize>, Option<usize>, Option<u32>) {
let packed = walk_call_graph_u64(payload, entry, 3, |p, s, e| {
let (data_off, info_off, n) = find_sample_loader_a3_in_body(p, s, e)?;
Some(((data_off as u64) << 32) | ((info_off as u64 & 0x00FF_FFFF) << 8) | (n as u64 & 0xFF))
});
match packed {
Some(v) => (
Some((v >> 32) as usize),
Some(((v >> 8) & 0x00FF_FFFF) as usize),
Some((v & 0xFF) as u32),
),
None => (None, None, None),
}
}
fn flat_scan_sample_loader(
payload: &[u8],
a3_relative: bool,
) -> (Option<usize>, Option<usize>, Option<u32>) {
const SCAN_END: usize = 0x2000;
let end = SCAN_END.min(payload.len());
let found = if a3_relative {
find_sample_loader_a3_in_body(payload, 0, end)
} else {
find_sample_loader_in_body(payload, 0, end)
};
match found {
Some((data, info, n)) => (Some(data), Some(info), Some(n)),
None => (None, None, None),
}
}
fn find_sample_loader_a3_in_body(
payload: &[u8],
start: usize,
end: usize,
) -> Option<(usize, usize, u32)> {
const FORWARD_DBF_WINDOW: usize = 0x40;
const BACKWARD_LEA_WINDOW: usize = 0x20;
let end_eff = end.min(payload.len().saturating_sub(1));
let mut i = start;
while i + 1 < end_eff {
if payload[i] == 0x72 {
let n = payload[i + 1] as u32 + 1;
if (1..=64).contains(&n) && has_backward_dbf_d1(payload, i + 2, end, FORWARD_DBF_WINDOW)
{
if let Some((data_off, info_off)) =
find_lea_a0_a1_a3_pair_before(payload, i, BACKWARD_LEA_WINDOW)
{
return Some((data_off, info_off, n));
}
}
}
i += 2;
}
None
}
fn find_lea_a0_a1_a3_pair_before(
payload: &[u8],
anchor: usize,
window: usize,
) -> Option<(usize, usize)> {
let start = anchor.saturating_sub(window);
let mut k = anchor.saturating_sub(4);
while k >= start {
if k + 7 < payload.len()
&& payload[k] == 0x41
&& payload[k + 1] == 0xEB
&& payload[k + 4] == 0x43
&& payload[k + 5] == 0xEB
{
let data_off = u16::from_be_bytes([payload[k + 2], payload[k + 3]]) as usize;
let info_off = u16::from_be_bytes([payload[k + 6], payload[k + 7]]) as usize;
if data_off < payload.len() && info_off < payload.len() {
return Some((data_off, info_off));
}
}
if k < 2 {
break;
}
k -= 2;
}
None
}
fn walk_for_sample_loader(
payload: &[u8],
entry: usize,
) -> (Option<usize>, Option<usize>, Option<u32>) {
let packed = walk_call_graph_u64(payload, entry, 3, |p, s, e| {
let (data_off, info_off, n) = find_sample_loader_in_body(p, s, e)?;
Some(((data_off as u64) << 32) | ((info_off as u64 & 0x00FF_FFFF) << 8) | (n as u64 & 0xFF))
});
match packed {
Some(v) => (
Some((v >> 32) as usize),
Some(((v >> 8) & 0x00FF_FFFF) as usize),
Some((v & 0xFF) as u32),
),
None => (None, None, None),
}
}
fn find_sample_loader_in_body(
payload: &[u8],
start: usize,
end: usize,
) -> Option<(usize, usize, u32)> {
const FORWARD_DBF_WINDOW: usize = 0x40;
const BACKWARD_LEA_WINDOW: usize = 0x20;
let end_eff = end.min(payload.len().saturating_sub(1));
let mut i = start;
while i + 1 < end_eff {
if payload[i] == 0x72 {
let n = payload[i + 1] as u32 + 1;
if (1..=64).contains(&n) && has_backward_dbf_d1(payload, i + 2, end, FORWARD_DBF_WINDOW)
{
if let Some((data_off, info_off)) =
find_lea_pair_before(payload, i, BACKWARD_LEA_WINDOW)
{
return Some((data_off, info_off, n));
}
}
}
i += 2;
}
None
}
fn find_lea_pair_before(payload: &[u8], anchor: usize, window: usize) -> Option<(usize, usize)> {
let start = anchor.saturating_sub(window);
let mut k = anchor.saturating_sub(4);
while k >= start {
let info_reg = k + 4 < payload.len() && (payload[k + 4] == 0x4B || payload[k + 4] == 0x43);
if k + 7 < payload.len()
&& payload[k] == 0x41
&& payload[k + 1] == 0xFA
&& info_reg
&& payload[k + 5] == 0xFA
{
let data_disp = i16::from_be_bytes([payload[k + 2], payload[k + 3]]) as i32;
let info_disp = i16::from_be_bytes([payload[k + 6], payload[k + 7]]) as i32;
let data_target = k as i32 + 2 + data_disp;
let info_target = (k + 4) as i32 + 2 + info_disp;
if data_target >= 0
&& info_target >= 0
&& (data_target as usize) < payload.len()
&& (info_target as usize) < payload.len()
{
return Some((data_target as usize, info_target as usize));
}
}
if k < 2 {
break;
}
k -= 2;
}
None
}
fn has_backward_dbf_d1(payload: &[u8], start: usize, end: usize, window: usize) -> bool {
let lim = (start + window)
.min(end)
.min(payload.len().saturating_sub(3));
let mut j = start;
while j + 3 <= lim {
if payload[j] == 0x51 && payload[j + 1] == 0xC9 && payload[j + 2] >= 0xF0 {
return true;
}
j += 2;
}
false
}
fn walk_call_graph_u64<F>(payload: &[u8], entry: usize, depth_limit: usize, probe: F) -> Option<u64>
where
F: Fn(&[u8], usize, usize) -> Option<u64>,
{
const BODY_LIMIT: usize = 0x400;
let mut visited: BTreeSet<usize> = BTreeSet::new();
let mut queue: VecDeque<(usize, usize)> = VecDeque::new();
queue.push_back((entry, 0));
while let Some((start, depth)) = queue.pop_front() {
if !visited.insert(start) {
continue;
}
let body_end = (start + BODY_LIMIT).min(payload.len());
if let Some(v) = probe(payload, start, body_end) {
return Some(v);
}
if depth >= depth_limit {
continue;
}
let mut i = start;
while i + 1 < body_end {
if payload[i] == 0x4E && payload[i + 1] == 0x75 {
break;
}
if payload[i] == 0x61 && payload[i + 1] == 0x00 && i + 4 <= body_end {
let disp = i16::from_be_bytes([payload[i + 2], payload[i + 3]]) as i32;
let target = i as i32 + 2 + disp;
if target >= 0 && (target as usize) < payload.len() {
queue.push_back((target as usize, depth + 1));
}
i += 4;
continue;
}
i += 2;
}
}
None
}
fn find_play_anchor(payload: &[u8], start: usize, end: usize) -> Option<usize> {
let end = end.min(payload.len().saturating_sub(10));
let mut i = start;
while i < end {
if payload[i] == 0x47
&& payload[i + 1] == 0xFA
&& payload[i + 4] == 0x4A
&& payload[i + 5] == 0x2B
&& payload[i + 8] == 0x67
{
return Some(i);
}
i += 2;
}
None
}
const PLAY_FEATURE_WINDOW: usize = 0x100;
fn detect_features(payload: &[u8], play_offset: usize) -> DwFeatures {
let mut f = DwFeatures::default();
let window_end = (play_offset + PLAY_FEATURE_WINDOW).min(payload.len());
if let Some(i) = find_pair(payload, play_offset, window_end, 0x10, 0x3A) {
f.enable_delay_counter = true;
if i + 7 < window_end && payload[i + 6] == 0xC0 && payload[i + 7] == 0xFC {
f.enable_delay_multiply = true;
}
}
if find_pair_with_gap(payload, play_offset, window_end, 0x20, 0x7A, 0x30, 0x3A, 2).is_some() {
f.enable_square_waveform = true;
}
if let Some(i) = find_pair(payload, play_offset, window_end, 0x6B, 0x00) {
if i + 5 < window_end && payload[i + 4] == 0xD0 && payload[i + 5] == 0x2D {
f.enable_sample_transpose = true;
}
}
let arp_env_end = 0x1000.min(payload.len());
if find_pair(payload, play_offset, arp_env_end, 0x21, 0x4A).is_some() {
f.enable_arpeggio = true;
if let Some(i) = find_pair(payload, play_offset, arp_env_end, 0x11, 0x6A) {
let _ = i;
f.enable_envelopes = true;
}
}
let handler_end = 0x1000.min(payload.len());
if find_quad(payload, play_offset, handler_end, 0x17, 0x59, 0x05, 0xF1).is_some() {
f.enable_volume_fade = true;
}
if find_quad(payload, play_offset, handler_end, 0x11, 0x59, 0x00, 0x08).is_some()
&& find_quad(payload, play_offset, handler_end, 0x11, 0x59, 0x00, 0x09).is_some()
{
f.enable_vibrato = true;
}
if find_quad(payload, play_offset, handler_end, 0xD0, 0x28, 0x00, 0x03).is_some() {
f.enable_channel_transpose = true;
}
if find_quad(payload, play_offset, handler_end, 0x0C, 0x11, 0x00, 0x83).is_some() {
f.note_repeat_is_tie = true;
}
f
}
fn find_quad(
payload: &[u8],
start: usize,
end: usize,
b0: u8,
b1: u8,
b2: u8,
b3: u8,
) -> Option<usize> {
let lim = end.min(payload.len().saturating_sub(3));
let mut i = start;
while i + 3 < lim {
if payload[i] == b0 && payload[i + 1] == b1 && payload[i + 2] == b2 && payload[i + 3] == b3
{
return Some(i);
}
i += 2;
}
None
}
fn find_pair(payload: &[u8], start: usize, end: usize, high: u8, low: u8) -> Option<usize> {
let lim = end.min(payload.len().saturating_sub(1));
let mut i = start;
while i + 1 < lim {
if payload[i] == high && payload[i + 1] == low {
return Some(i);
}
i += 2;
}
None
}
#[allow(clippy::too_many_arguments)] fn find_pair_with_gap(
payload: &[u8],
start: usize,
end: usize,
h1: u8,
l1: u8,
h2: u8,
l2: u8,
gap: usize,
) -> Option<usize> {
let lim = end.min(payload.len().saturating_sub(3 + gap));
let mut i = start;
while i + 3 + gap < lim {
if payload[i] == h1
&& payload[i + 1] == l1
&& payload[i + 2 + gap] == h2
&& payload[i + 3 + gap] == l2
{
return Some(i);
}
i += 2;
}
None
}
fn find_period_table_via_lea(payload: &[u8]) -> Option<PeriodTable> {
let mut i = 0;
while i + 10 <= payload.len() {
if payload[i] == 0x45
&& payload[i + 1] == 0xFA
&& payload[i + 4] == 0x32
&& payload[i + 5] == 0x2D
&& payload[i + 6] == 0x00
&& (payload[i + 7] == 0x0A || payload[i + 7] == 0x0C)
&& payload[i + 8] == 0x74
&& payload[i + 9] == 0x0A
{
if let Some(table_off) = read_lea_a2_at(payload, i) {
if let Some(bytes) = payload.get(table_off..table_off + 2) {
let w = u16::from_be_bytes([bytes[0], bytes[1]]);
return match w {
8192 => Some(PeriodTable::P3),
4096 => Some(PeriodTable::P2),
_ => None,
};
}
}
}
i += 2;
}
None
}
fn find_instrument_volume_table(payload: &[u8]) -> Option<usize> {
let mut i = 0;
while i + 8 <= payload.len() {
if payload[i] == 0x4B
&& payload[i + 1] == 0xFA
&& payload[i + 4] == 0xC0
&& payload[i + 5] == 0xFC
&& payload[i + 6] == 0x00
&& payload[i + 7] == 0x10
{
let disp = i16::from_be_bytes([payload[i + 2], payload[i + 3]]) as isize;
let base = i as isize + 2 + disp;
if base >= 0 && (base as usize) < payload.len() {
return Some(base as usize);
}
}
i += 2;
}
None
}
fn guess_new_period_table(payload: &[u8]) -> Option<PeriodTable> {
let mut i = 0;
while i + 2 <= payload.len() {
let w = u16::from_be_bytes([payload[i], payload[i + 1]]);
if w == 8192 {
return Some(PeriodTable::P3);
}
if w == 4096 {
return Some(PeriodTable::P2);
}
i += 2;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_short_payloads() {
assert!(detect(&[0u8; 4]).is_none());
}
#[test]
fn rejects_sc68_magic() {
let mut buf = [0u8; 64];
buf[..4].copy_from_slice(&SC68_MAGIC);
assert!(detect(&buf).is_none());
}
#[test]
fn rejects_non_stub_opening() {
let buf = [0xFFu8; 64];
assert!(detect(&buf).is_none());
}
#[test]
fn finds_leviathan_instrument_volume_table() {
let mut buf = vec![0u8; 64];
buf[4..12].copy_from_slice(&[0x4B, 0xFA, 0x00, 0x0A, 0xC0, 0xFC, 0x00, 0x10]);
assert_eq!(find_instrument_volume_table(&buf), Some(16));
let mut buf2 = vec![0u8; 64];
buf2[4..12].copy_from_slice(&[0x4B, 0xFA, 0x00, 0x0A, 0xC0, 0xFC, 0x00, 0x0C]);
assert_eq!(find_instrument_volume_table(&buf2), None);
}
}