Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 11 additions & 78 deletions core/common/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,55 +163,9 @@ impl Slice {
}
}

/// Create a subslice of this buffer slice, without bounds checking.
///
/// The parameter `slice` must be derived from the same buffer this slice
/// was, otherwise the returned slice will be empty
///
/// This emulates "unbounded reads" in file formats that don't bounds-check
/// things properly.
pub fn to_unbounded_subslice(&self, slice: &[u8]) -> Self {
let self_guard = self.buf.0.read().expect("unlock read");
let self_pval = self_guard.as_ptr() as usize;
let self_len = self.buf.len();
let slice_pval = slice.as_ptr() as usize;

if self_pval <= slice_pval && slice_pval < (self_pval + self_len) {
Slice {
buf: self.buf.clone(),
start: slice_pval - self_pval,
end: (slice_pval - self_pval) + slice.len(),
}
} else {
self.buf.to_empty_slice()
}
}

/// Construct a new Slice from a start and an end.
///
/// The start and end values will be relative to the current slice.
/// Furthermore, this function will yield an empty slice if the calculated
/// slice would be invalid (e.g. negative length) or would extend past the
/// end of the current slice.
pub fn to_start_and_end(&self, start: usize, end: usize) -> Self {
let new_start = self.start + start;
let new_end = self.start + end;

if new_start <= new_end && new_end < self.end {
if let Some(result) = self.buf.get(new_start..new_end) {
result
} else {
self.buf.to_empty_slice()
}
} else {
self.buf.to_empty_slice()
}
}

/// Get a subslice of this slice.
///
/// Normal subslicing bounds rules will be respected. If you want to get a
/// slice outside the bounds of this one, use `to_unbounded_subslice`.
/// Normal subslicing bounds rules will be respected.
pub fn get<T: RangeBounds<usize>>(&self, range: T) -> Option<Slice> {
let s = self.buf.0.read().expect("unlock read");

Expand Down Expand Up @@ -267,13 +221,13 @@ impl Slice {
pub fn buffer(&self) -> &Buffer {
&self.buf
}
}

/// Create a readable cursor into the `Slice`.
pub fn as_cursor(&self) -> SliceCursor {
SliceCursor {
slice: self.clone(),
pos: 0,
}
impl Read for Slice {
fn read(&mut self, data: &mut [u8]) -> IoResult<usize> {
let len = <&[u8] as Read>::read(&mut self.data().as_ref(), data)?;
self.start += len;
Ok(len)
}
}

Expand All @@ -289,27 +243,6 @@ impl UpperHex for Slice {
}
}

/// A readable cursor into a buffer slice.
pub struct SliceCursor {
slice: Slice,
pos: usize,
}

impl Read for SliceCursor {
fn read(&mut self, data: &mut [u8]) -> IoResult<usize> {
let copy_count = min(data.len(), self.slice.len() - self.pos);
let slice = self
.slice
.get(self.pos..self.pos + copy_count)
.expect("Slice offsets are always valid");
let slice_data = slice.data();

data[..copy_count].copy_from_slice(&slice_data);
self.pos += copy_count;
Ok(copy_count)
}
}

#[derive(Debug, Error)]
pub enum SubstreamError {
#[error("Attempted to add substream chunk from a foreign buffer")]
Expand Down Expand Up @@ -604,12 +537,12 @@ mod test {
}

#[test]
fn slice_cursor_read() {
fn slice_read() {
let buf = Buffer::from(vec![
38, 26, 99, 1, 1, 1, 1, 38, 12, 14, 1, 1, 93, 86, 1, 88,
]);
let slice = buf.to_full_slice();
let mut cursor = slice.as_cursor();
let mut cursor = slice.clone();
let refdata = slice.data();

let mut data = vec![0; 1];
Expand All @@ -622,12 +555,12 @@ mod test {
}

#[test]
fn slice_cursor_read_all() {
fn slice_read_all() {
let buf = Buffer::from(vec![
38, 26, 99, 1, 1, 1, 1, 38, 12, 14, 1, 1, 93, 86, 1, 88,
]);
let slice = buf.to_full_slice();
let mut cursor = slice.as_cursor();
let mut cursor = slice.clone();
let refdata = slice.data();

let mut data = vec![0; slice.len() + 32];
Expand Down
57 changes: 6 additions & 51 deletions core/common/src/tag_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,61 +388,16 @@ impl SwfSlice {
}
}

/// Construct a new SwfSlice from a Reader and a size.
/// Construct a new SwfSlice from a SwfStream.
///
/// This is intended to allow constructing references to the contents of a
/// given SWF tag. You just need the current reader and the size of the tag
/// you want to reference.
/// given SWF tag.
///
/// The returned slice may or may not be a subslice of the current slice.
/// If the resulting slice would be outside the bounds of the underlying
/// movie, or the given reader refers to a different underlying movie, this
/// If the reader references a slice outside the bounds of the current
/// slice, or the given reader refers to a different underlying movie, this
/// function returns an empty slice.
pub fn resize_to_reader(&self, reader: &mut SwfStream<'_>, size: usize) -> Self {
if self.movie.data().as_ptr() as usize <= reader.get_ref().as_ptr() as usize
&& (reader.get_ref().as_ptr() as usize)
< self.movie.data().as_ptr() as usize + self.movie.data().len()
{
let outer_offset =
reader.get_ref().as_ptr() as usize - self.movie.data().as_ptr() as usize;
let new_start = outer_offset;
let new_end = outer_offset + size;

let len = self.movie.data().len();

if new_start < len && new_end < len {
Self {
movie: self.movie.clone(),
start: new_start,
end: new_end,
}
} else {
self.copy_empty()
}
} else {
self.copy_empty()
}
}

/// Construct a new SwfSlice from a start and an end.
///
/// The start and end values will be relative to the current slice.
/// Furthermore, this function will yield an empty slice if the calculated slice
/// would be invalid (e.g. negative length) or would extend past the end of
/// the current slice.
pub fn to_start_and_end(&self, start: usize, end: usize) -> Self {
let new_start = self.start + start;
let new_end = self.start + end;

if new_start <= new_end {
if let Some(result) = self.movie.data().get(new_start..new_end) {
self.to_subslice(result)
} else {
self.copy_empty()
}
} else {
self.copy_empty()
}
pub fn resize_to_reader(&self, reader: &SwfStream<'_>) -> Self {
self.to_subslice(reader.get_ref())
}

/// Convert the SwfSlice into a standard data slice.
Expand Down
2 changes: 1 addition & 1 deletion core/src/avm2/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@ pub fn load_playerglobal<'gc>(context: &mut UpdateContext<'gc>, domain: Domain<'

let mut reader = slice.read_from(0);

let tag_callback = |reader: &mut SwfStream<'_>, tag_code, _tag_len| {
let tag_callback = |reader: &mut SwfStream<'_>, tag_code| {
if tag_code == TagCode::DoAbc2 {
let do_abc = reader
.read_do_abc_2()
Expand Down
76 changes: 33 additions & 43 deletions core/src/backend/audio/decoders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,47 +297,43 @@
type Item = SwfSlice;

fn next(&mut self) -> Option<Self::Item> {
let audio_data = &mut self.current_audio_data;
let compression = self.compression;
let mut found = false;

let swf_data = &self.swf_data;
loop {
let tag_callback =
|reader: &mut swf::read::Reader<'_>, tag_code, tag_len| match tag_code {
TagCode::SoundStreamBlock if !found => {
found = true;
let mut audio_block = &reader.get_ref()[..tag_len];
// MP3 audio blocks start with a header indicating sample count + seek offset (SWF19 p.184).
if compression == AudioCompression::Mp3 && audio_block.len() >= 4 {
// MP3s deliver audio in frames of 576 samples, which means we may have SoundStreamBlocks with
// lots of extra samples, followed by a block with 0 samples. Worse, there may be frames without
// blocks at all despite SWF19 saying this shouldn't happen. This may or may not indicate a gap
// in the audio depending on the number of empty frames.
// Keep a tally of the # of samples we've seen compared to the number of samples that will be
// played in each timeline frame. Only stop an MP3 sound if we've exhausted all of the samples.
// RESEARCHME: How does Flash Player actually determine when there is an audio gap or not?
// If an MP3 audio track has gaps, Flash Player will often play it out of sync (too early).
// Seems closely related to `stream_info.num_samples_per_block`.
let num_samples = u16::from_le_bytes(
audio_block[..2].try_into().expect("2 bytes fit into a u16"),
);
self.mp3_samples_buffered += i32::from(num_samples);
audio_block = &audio_block[4..];
}
*audio_data = swf_data.to_subslice(audio_block);
Ok(ControlFlow::Continue)
}
TagCode::ShowFrame if compression == AudioCompression::Mp3 => {
self.mp3_samples_buffered -= i32::from(self.mp3_samples_per_block);
Ok(ControlFlow::Exit)
}
TagCode::ShowFrame => Ok(ControlFlow::Exit),
_ => Ok(ControlFlow::Continue),
};

let mut reader = self.swf_data.read_from(self.pos as u64);
let _ = crate::tag_utils::decode_tags(&mut reader, tag_callback);
let _ = crate::tag_utils::decode_tags(&mut reader, |reader, tag_code| match tag_code {
TagCode::SoundStreamBlock if !found => {
found = true;
let mut audio_block = reader.get_ref();

Check warning on line 309 in core/src/backend/audio/decoders.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (306–309)
// MP3 audio blocks start with a header indicating sample count + seek offset (SWF19 p.184).
if compression == AudioCompression::Mp3
&& let [b0, b1, _, _, ref block_data @ ..] = *audio_block
{
// MP3s deliver audio in frames of 576 samples, which means we may have SoundStreamBlocks with
// lots of extra samples, followed by a block with 0 samples. Worse, there may be frames without
// blocks at all despite SWF19 saying this shouldn't happen. This may or may not indicate a gap
// in the audio depending on the number of empty frames.
// Keep a tally of the # of samples we've seen compared to the number of samples that will be
// played in each timeline frame. Only stop an MP3 sound if we've exhausted all of the samples.
// RESEARCHME: How does Flash Player actually determine when there is an audio gap or not?
// If an MP3 audio track has gaps, Flash Player will often play it out of sync (too early).
// Seems closely related to `stream_info.num_samples_per_block`.
let num_samples = u16::from_le_bytes([b0, b1]);
self.mp3_samples_buffered += i32::from(num_samples);
audio_block = block_data;
}
self.current_audio_data = swf_data.to_subslice(audio_block);
Ok(ControlFlow::Continue)

Check warning on line 328 in core/src/backend/audio/decoders.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (311–328)
}
TagCode::ShowFrame if compression == AudioCompression::Mp3 => {
self.mp3_samples_buffered -= i32::from(self.mp3_samples_per_block);
Ok(ControlFlow::Exit)

Check warning on line 332 in core/src/backend/audio/decoders.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (330–332)
}
TagCode::ShowFrame => Ok(ControlFlow::Exit),
_ => Ok(ControlFlow::Continue),
});

Check warning on line 336 in core/src/backend/audio/decoders.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (334–336)
self.pos = reader.get_ref().as_ptr() as usize - swf_data.as_ref().as_ptr() as usize;

// If we hit a SoundStreamBlock within this frame, return it. Otherwise, the stream should end.
Expand Down Expand Up @@ -474,15 +470,9 @@
return Ok(0);
}

let chunk = self.current_audio_data.as_mut().unwrap();
let data = chunk.data();

//At this point, current_audio_data should be full
let len = std::cmp::min(buf.len(), data.len());
buf[..len].copy_from_slice(&data[..len]);

drop(data);
*chunk = chunk.to_start_and_end(len, chunk.len());
let chunk = self.current_audio_data.as_mut().unwrap();
let len = chunk.read(buf)?;

Check warning on line 475 in core/src/backend/audio/decoders.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (474–475)

if chunk.is_empty() {
self.current_audio_data = None;
Expand Down
15 changes: 6 additions & 9 deletions core/src/backend/audio/decoders/adpcm.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{Decoder, SeekableDecoder, SoundStreamInfo, Substream, SubstreamTagReader};
use bitstream_io::{BigEndian, BitRead, BitReader};
use ruffle_common::buffer::SliceCursor;
use ruffle_common::buffer::Slice;
use std::io::{Cursor, Read};
use swf::SoundFormat;
use thiserror::Error;
Expand Down Expand Up @@ -191,7 +191,7 @@
pub struct AdpcmSubstreamDecoder {
format: SoundFormat,
tag_reader: SubstreamTagReader,
decoder: AdpcmDecoder<SliceCursor>,
decoder: AdpcmDecoder<Slice>,
}

impl AdpcmSubstreamDecoder {
Expand All @@ -200,7 +200,7 @@
let mut tag_reader = SubstreamTagReader::new(stream_info, data_stream);
let audio_data = tag_reader.next().unwrap_or(empty_buffer);
let decoder = AdpcmDecoder::new(
audio_data.as_cursor(),
audio_data,

Check warning on line 203 in core/src/backend/audio/decoders/adpcm.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (203)
stream_info.stream_format.is_stereo,
stream_info.stream_format.sample_rate,
)?;
Expand Down Expand Up @@ -234,12 +234,9 @@
// We've reached the end of the sound stream block tag, so
// read the next one and recreate the decoder.
// `AdpcmDecoder` read the ADPCM header when it is created.
self.decoder = AdpcmDecoder::new(
audio_data.as_cursor(),
self.format.is_stereo,
self.format.sample_rate,
)
.ok()?;
self.decoder =
AdpcmDecoder::new(audio_data, self.format.is_stereo, self.format.sample_rate)
.ok()?;

Check warning on line 239 in core/src/backend/audio/decoders/adpcm.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (237–239)
self.decoder.next()
} else {
// No more SoundStreamBlock tags.
Expand Down
2 changes: 1 addition & 1 deletion core/src/display_object/avm1_button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl<'gc> Avm1Button<'gc> {
.actions
.iter()
.map(|action| ButtonAction {
action_data: source_movie.to_unbounded_subslice(action.action_data),
action_data: source_movie.to_subslice(action.action_data),
conditions: action.conditions,
})
.collect();
Expand Down
Loading
Loading