From afbc4eac5c7834c56688a378a1de2fef3c2b309e Mon Sep 17 00:00:00 2001 From: m35 Date: Sun, 15 Dec 2019 09:29:25 -0700 Subject: [PATCH] v1.03 rev3953 (15 Dec 2019) Bug fixes: - Some videos in EA games are still not detected --- jpsxdec/PSXListOFGames.txt | 28 ++-- jpsxdec/build.xml | 2 +- jpsxdec/doc/CHANGES.txt | 3 + jpsxdec/src/jpsxdec/Version.java | 2 +- .../modules/eavideo/DiscIndexerEAVideo.java | 9 +- .../modules/eavideo/EAVideoPacket.java | 146 +++++++++--------- .../modules/eavideo/EAVideoPacketSectors.java | 5 + .../modules/eavideo/EAVideoStreamReader.java | 43 ++++-- .../modules/eavideo/SectorClaimToEAVideo.java | 13 +- .../util/PushAvailableInputStream.java | 11 +- 10 files changed, 152 insertions(+), 110 deletions(-) diff --git a/jpsxdec/PSXListOFGames.txt b/jpsxdec/PSXListOFGames.txt index 8e3834d..a299112 100644 --- a/jpsxdec/PSXListOFGames.txt +++ b/jpsxdec/PSXListOFGames.txt @@ -557,7 +557,7 @@ Status [ ] SLUS-01172 Frogger 2 - Swampy's Revenge [ ] SLUS-01011 Front Mission 3 [ ] SLUS-90034 Future Cop - L.A.P.D. [Demo] -[ ] SLUS-00739 Future Cop L.A.P.D. +[+] SLUS-00739 Future Cop L.A.P.D. [ ] SLUS-80690 G Darius, Devil Dice, Brunswick Circuit Bowling (Trade) [Demo] [ ] SLUS-01258 Galaga - Destination Earth [ ] SLUS-90077 Galerians [Demo] @@ -791,7 +791,7 @@ Status [ ] SLUS-01306 M & M's Shell Shocked [ ] SLUS-00383 Machine Head [ ] SLUS-00470 Machine Hunter -[ ] SLUS-00961 Madden NFL 2000 +[+] SLUS-00961 Madden NFL 2000 [ ] SLUS-01241 Madden NFL 2001 [ ] SLUS-01402 Madden NFL 2002 [ ] SLUS-01482 Madden NFL 2003 @@ -915,19 +915,19 @@ Status [ ] SLUS-00416 Namco Museum Vol.4 [ ] SLUS-00417 Namco Museum Vol.5 [ ] SLUS-00325 Nanotek Warrior -[ ] SLUS-00962 NASCAR 2000 -[ ] SLUS-01263 NASCAR 2001 -[ ] SLUS-00521 NASCAR '98 -[ ] SLUS-00647 NASCAR '98 - Collector's Edition -[ ] SLUS-00740 NASCAR '99 -[ ] SLUS-00883 NASCAR '99 Legacy +[+] SLUS-00962 NASCAR 2000 +[+] SLUS-01263 NASCAR 2001 +[+] SLUS-00521 NASCAR '98 +[+] SLUS-00647 NASCAR '98 - Collector's Edition +[+] SLUS-00740 NASCAR '99 +[+] SLUS-00883 NASCAR '99 Legacy [ ] SLUS-01166 NASCAR Heat [ ] SLUS-01050 NASCAR Racers [ ] SLUS-00374 NASCAR Racing [+] SLUS-01068 NASCAR Rumble -[ ] SLUS-01403 NASCAR Thunder 2002 -[ ] SLUS-01502 NASCAR Thunder 2003 -[ ] SLUS-01571 NASCAR Thunder 2004 +[+] SLUS-01403 NASCAR Thunder 2002 +[+] SLUS-01502 NASCAR Thunder 2003 +[+] SLUS-01571 NASCAR Thunder 2004 [ ] SLUS-00926 NBA Basketball 2000 [ ] SLUS-00492 NBA Fastbreak '98 [ ] SLUS-00329 NBA Hangtime @@ -1315,7 +1315,7 @@ Status [ ] SLUS-00565 Riven - The Sequel to Myst [Disc4of5] [ ] SLUS-00580 Riven - The Sequel to Myst [Disc5of5] [ ] SLUS-00035 Road Rash -[ ] SLUS-01053 Road Rash - Jailbreak +[+] SLUS-01053 Road Rash - Jailbreak [+] SLUS-00524 Road Rash 3D [ ] SLUS-01024 Roadsters [ ] SLUS-00316 Robo-Pit @@ -1489,8 +1489,8 @@ Status [o] SLUS-00418 Super Puzzle Fighter II Turbo [ ] SLUS-01464 Super Shot Soccer [ ] SLUS-01052 Superbikes 2000 -[ ] SLUS-01005 SuperCross 2000 -[ ] SLUS-01319 SuperCross 2001 +[+] SLUS-01005 SuperCross 2000 +[+] SLUS-01319 SuperCross 2001 [ ] SCUS-94453 SuperCross Circuit [ ] SCUS-94396 Supercross Circuit [Demo] [ ] SLUS-00712 Superman diff --git a/jpsxdec/build.xml b/jpsxdec/build.xml index 9d09598..5b4d7b6 100644 --- a/jpsxdec/build.xml +++ b/jpsxdec/build.xml @@ -9,7 +9,7 @@ - + diff --git a/jpsxdec/doc/CHANGES.txt b/jpsxdec/doc/CHANGES.txt index 860879d..00335bf 100644 --- a/jpsxdec/doc/CHANGES.txt +++ b/jpsxdec/doc/CHANGES.txt @@ -1,3 +1,6 @@ +v1.03 rev3953 (15 Dec 2019) + Bug fixes: + - Some videos in EA games are still not detected v1.02 rev3950 (13 Dec 2019) - Added support for Jackie Chan Stuntmaster videos Bug fixes: diff --git a/jpsxdec/src/jpsxdec/Version.java b/jpsxdec/src/jpsxdec/Version.java index 1f743ab..afabccd 100644 --- a/jpsxdec/src/jpsxdec/Version.java +++ b/jpsxdec/src/jpsxdec/Version.java @@ -39,7 +39,7 @@ public class Version { - public final static String Version = "1.02 (beta)"; + public final static String Version = "1.03 (beta)"; public final static String IndexHeader = "[jPSXdec v"+Version+"]"; } diff --git a/jpsxdec/src/jpsxdec/modules/eavideo/DiscIndexerEAVideo.java b/jpsxdec/src/jpsxdec/modules/eavideo/DiscIndexerEAVideo.java index be5501d..79bb91e 100644 --- a/jpsxdec/src/jpsxdec/modules/eavideo/DiscIndexerEAVideo.java +++ b/jpsxdec/src/jpsxdec/modules/eavideo/DiscIndexerEAVideo.java @@ -127,7 +127,7 @@ public MovieBuilder(int iStartSector) { _iEndSector = iStartSector; } - public void addPacket(@Nonnull EAVideoPacketSectors packet, int iStartSector) throws BinaryDataNotRecognized { + public void addPacket(@Nonnull EAVideoPacketSectors packet) throws BinaryDataNotRecognized { _iEndSector = packet.iEndSector; if (packet.packet instanceof EAVideoPacket.AU) { EAVideoPacket.AU au = (EAVideoPacket.AU)packet.packet; @@ -170,7 +170,12 @@ public void feedPacket(@Nonnull EAVideoPacketSectors packet, @Nonnull ILocalized } _movieBuilder = new MovieBuilder(packet.iStartSector); } else { - _movieBuilder.addPacket(packet, 0); + if (packet.packet instanceof EAVideoPacket.VLC0) { + endVideo(log); + _movieBuilder = new MovieBuilder(packet.iStartSector); + } else { + _movieBuilder.addPacket(packet); + } } } catch (BinaryDataNotRecognized ex) { LOG.log(Level.SEVERE, "EA video data corruption", ex); diff --git a/jpsxdec/src/jpsxdec/modules/eavideo/EAVideoPacket.java b/jpsxdec/src/jpsxdec/modules/eavideo/EAVideoPacket.java index 6f7a31a..a5cb5b5 100644 --- a/jpsxdec/src/jpsxdec/modules/eavideo/EAVideoPacket.java +++ b/jpsxdec/src/jpsxdec/modules/eavideo/EAVideoPacket.java @@ -62,14 +62,10 @@ /** * Support for videos found in several Electronic Arts games. - * Most of the time videos are found in files with .WVE extention. - * Known games: - * - Road Rash 3D - * - PlayStation Magazine Demo Disc 14 - * - NASCAR Rumble - * - Madden 99 - * It takes a unique approach to bitstreams, every movie has its own unique VLC table. - * There's a lot of big-endian data in here. + * Many of the videos are found in files with .WVE extention. + * Many of the EA Sports titles use this video format. + * + * It takes a unique approach to bitstreams: every movie has its own unique VLC table. */ public abstract class EAVideoPacket { @@ -78,7 +74,7 @@ public abstract class EAVideoPacket { public static final int SAMPLE_FRAMES_PER_SECOND = 22050; public static final int SAMPLE_FRAMES_PER_SECTOR = SAMPLE_FRAMES_PER_SECOND / 150; static { - if (SAMPLE_FRAMES_PER_SECOND % 150 != 0) // assert + if (SAMPLE_FRAMES_PER_SECOND % 150 != 0) // assert sanity check throw new RuntimeException("EA video sample rate doesn't cleanly divide by sector rate"); } @@ -93,59 +89,53 @@ public abstract class EAVideoPacket { /** 10 minutes. */ private static final int MAX_PRESENTATION_SAMPLE_FRAME = SAMPLE_FRAMES_PER_SECOND * 60 * 10; /** 10 minutes. */ - private static final int MAX_FRAME_NUMBER = FRAMES_PER_SECOND * 60* 10; + private static final int MAX_FRAME_NUMBER = FRAMES_PER_SECOND * 60 * 10; private static final int MIN_FRAME_DIMENSIONS = 16; private static final int MAX_FRAME_DIMENSIONS = 640; + // Every packet begins with one of these magic values (big-endian) public static final long MAGIC_VLC0 = 0x564c4330; private static final long MAGIC_MDEC = 0x4d444543; private static final long MAGIC_au00 = 0x61753030; private static final long MAGIC_au01 = 0x61753031; - public static final AudioFormat EA_VIDEO_AUDIO_FORMAT = new AudioFormat(SAMPLE_FRAMES_PER_SECOND, 16, 2, true, false); + public static @Nonnull Type readHeaderType(@Nonnull InputStream is) + throws EOFException, IOException, BinaryDataNotRecognized + { + long lngPacketType = IO.readUInt32BE(is); + if (lngPacketType == MAGIC_au00) + return Type.au00; + if (lngPacketType == MAGIC_au01) + return Type.au01; + if (lngPacketType == MAGIC_MDEC) + return Type.MDEC; + if (lngPacketType == 0) + return Type.ZEROES; + if (lngPacketType == MAGIC_VLC0) + return Type.VLC0; + throw new BinaryDataNotRecognized("Unknown packet type %08x", lngPacketType); + } + public static enum Type { + VLC0, + MDEC, + au00, + au01, + ZEROES; // basically means there's no more data - private static int _iMinPacketSize = Integer.MAX_VALUE; - private static int _iMaxPacketSize = 0; + public static final int SIZEOF = 4; - public static class Header { - public static final int SIZEOF = 8; + public int bytesNeededToFinishHeader() { + return 4; + } - public static Header read(@Nonnull InputStream is) - throws EOFException, IOException, BinaryDataNotRecognized - { - return read(is, true); + public @Nonnull Header readHeader(@Nonnull InputStream is) throws EOFException, IOException, BinaryDataNotRecognized { + return doReadHeader(is, true); } - private static Header read(@Nonnull InputStream is, boolean blnThrowEx) + private @CheckForNull Header doReadHeader(@Nonnull InputStream is, boolean blnThrowEx) throws EOFException, IOException, BinaryDataNotRecognized { - long lngPacketType = IO.readUInt32BE(is); - - // check for all zeroes, will mean the end - if (lngPacketType == 0) { - int iHeaderPacketSize = IO.readSInt32BE(is); - if (iHeaderPacketSize != 0) { - if (blnThrowEx) - throw new BinaryDataNotRecognized("0 packet type with non zero header %08x", iHeaderPacketSize); - else - return null; - } - - return new Header(0, 0); - } - - if (lngPacketType != MAGIC_VLC0 && - lngPacketType != MAGIC_au00 && - lngPacketType != MAGIC_au01 && - lngPacketType != MAGIC_MDEC) - { - if (blnThrowEx) - throw new BinaryDataNotRecognized("Unknown packet type %08x", lngPacketType); - else - return null; - } - int iHeaderPacketSize = IO.readSInt32BE(is); if (iHeaderPacketSize % 4 != 0) { if (blnThrowEx) @@ -165,48 +155,53 @@ private static Header read(@Nonnull InputStream is, boolean blnThrowEx) if (iHeaderPacketSize > _iMaxPacketSize) _iMaxPacketSize = iHeaderPacketSize; - return new Header(lngPacketType, iHeaderPacketSize); + return new Header(this, iHeaderPacketSize); } + } + + public static final AudioFormat EA_VIDEO_AUDIO_FORMAT = new AudioFormat(SAMPLE_FRAMES_PER_SECOND, 16, 2, true, false); + + private static int _iMinPacketSize = Integer.MAX_VALUE; + private static int _iMaxPacketSize = 0; + + public static class Header { - private final long _lngPacketType; // @0 4 bytes (BE) + @Nonnull + private final Type _packetType; // @0 4 bytes BE private final int _iHeaderPacketSize; // @4 4 bytes BE - private Header(long lngPacketType, int iHeaderPacketSize) { - _lngPacketType = lngPacketType; + private Header(@Nonnull Type type, int iHeaderPacketSize) { + _packetType = type; _iHeaderPacketSize = iHeaderPacketSize; } - public long getPacketType() { - return _lngPacketType; + public @Nonnull Type getPacketType() { + return _packetType; } public int getPayloadSize() { return _iHeaderPacketSize - 8; } - public boolean isEndPacket() { - return _iHeaderPacketSize == 0; - } - public @Nonnull EAVideoPacket readPacket(@Nonnull InputStream is) throws EOFException, IOException, BinaryDataNotRecognized { - if (_lngPacketType == MAGIC_au00 || _lngPacketType == MAGIC_au01) { + if (_packetType == Type.au00 || _packetType == Type.au01) { return new AU(this, is); } - if (_lngPacketType == MAGIC_MDEC) { + if (_packetType == Type.MDEC) { return new MDEC(this, is); } - if (_lngPacketType == MAGIC_VLC0) { + if (_packetType == Type.VLC0) { return VLC0.read(this, is, true); } - throw new BinaryDataNotRecognized("Trying to read not a packet " + this); + throw new RuntimeException("?"); } @Override public String toString() { - return String.format("Header %08x size %d", _lngPacketType, _iHeaderPacketSize); + return String.format("Header %s size %d", _packetType, _iHeaderPacketSize); } } @@ -220,8 +215,8 @@ private EAVideoPacket(@Nonnull Header header) { _header = header; } - public long getPacketType() { - return _header._lngPacketType; + public @Nonnull Type getPacketType() { + return _header._packetType; } public int getPacketSizeInHeader() { @@ -234,7 +229,11 @@ public int getPacketSizeInHeader() { public static @CheckForNull VLC0 readVlc0(@Nonnull InputStream is) throws EOFException, IOException { try { - Header header = Header.read(is, false); + Type type = readHeaderType(is); + if (type != Type.VLC0) + return null; + + Header header = type.doReadHeader(is, false); if (header == null) return null; @@ -251,7 +250,10 @@ public int getPacketSizeInHeader() { */ public static class VLC0 extends EAVideoPacket { - public static final int SIZEOF = Header.SIZEOF + EA_VIDEO_BIT_CODE_ORDER.length * 2; + // 4 bytes for packet type + // + 4 bytes for packet size + // + size of VLC table (should always be the same) + public static final int SIZEOF = Type.SIZEOF + 4 + EA_VIDEO_BIT_CODE_ORDER.length * 2; private static VLC0 read(@Nonnull Header header, @Nonnull InputStream is, boolean blnThrowEx) throws BinaryDataNotRecognized, EOFException, IOException @@ -324,6 +326,7 @@ public void printCodes() { public String toString() { return "VLC0"; } + } // ######################################################################### @@ -367,11 +370,13 @@ public AU(@Nonnull Header header, @Nonnull InputStream is) if (i512 != 512) throw new BinaryDataNotRecognized("%d != 512", i512); + // -8 for header magic + header size + // -8 for 2048 and 512 _abCompressedSpu = IO.readByteArray(is, header._iHeaderPacketSize - 8 - 8); } public boolean isLastAudioPacket() { - return getPacketType() == MAGIC_au01; + return getPacketType() == Type.au01; } public int calcSampleFramesGenerated() { @@ -398,7 +403,7 @@ public int getSpuSoundUnitPairCount() { } } - if (out.size() != calcSampleFramesGenerated() * 4) + if (out.size() != calcSampleFramesGenerated() * 4) // sanity check throw new RuntimeException(); Fraction presentationSector = new Fraction(_iPresentationSampleFrame, SAMPLE_FRAMES_PER_SECTOR); @@ -408,8 +413,8 @@ public int getSpuSoundUnitPairCount() { @Override public String toString() { - return String.format("au0%c start sample frame:%d sample frames:%d", - isLastAudioPacket() ? '1' : '0', + return String.format("%s start sample frame:%d sample frames:%d", + getPacketType().name(), _iPresentationSampleFrame, calcSampleFramesGenerated()); } @@ -434,6 +439,7 @@ public String toString() { // Maybe the leftover 2 bytes in some audio packets? // Maybe the timestamp? // More debugging is nedded + // In any case, it's not necessary for just decoding the audio abSpuSoundUnit[1] = 0; System.arraycopy(abPayload, iBytesUsed+1, abSpuSoundUnit, 2, 14); units.add(new SpuAdpcmSoundUnit(abSpuSoundUnit)); @@ -527,7 +533,7 @@ public int getQuantizationScale() { return _strHeader.getQuantizationScale(); } - public byte[] getBitstream() { + public @Nonnull byte[] getBitstream() { return _abBitstream; } diff --git a/jpsxdec/src/jpsxdec/modules/eavideo/EAVideoPacketSectors.java b/jpsxdec/src/jpsxdec/modules/eavideo/EAVideoPacketSectors.java index 8051468..8560d93 100644 --- a/jpsxdec/src/jpsxdec/modules/eavideo/EAVideoPacketSectors.java +++ b/jpsxdec/src/jpsxdec/modules/eavideo/EAVideoPacketSectors.java @@ -50,4 +50,9 @@ public EAVideoPacketSectors(@Nonnull EAVideoPacket packet, int iStartSector, int this.iStartSector = iStartSector; this.iEndSector = iEndSector; } + + @Override + public String toString() { + return String.format("[%d-%d] %s", iStartSector, iEndSector, packet.toString()); + } } diff --git a/jpsxdec/src/jpsxdec/modules/eavideo/EAVideoStreamReader.java b/jpsxdec/src/jpsxdec/modules/eavideo/EAVideoStreamReader.java index a3adbae..5d44633 100644 --- a/jpsxdec/src/jpsxdec/modules/eavideo/EAVideoStreamReader.java +++ b/jpsxdec/src/jpsxdec/modules/eavideo/EAVideoStreamReader.java @@ -52,6 +52,9 @@ public class EAVideoStreamReader { @Nonnull private final PushAvailableInputStream _sectorStream = new PushAvailableInputStream(); + @CheckForNull + private EAVideoPacket.Type _headerType; + @CheckForNull private EAVideoPacket.Header _header; @@ -62,7 +65,7 @@ public boolean isEnd() { return _blnEnd; } - public @Nonnull SectorEAVideo readSectorPackets(@Nonnull CdSector sector, int iSkip, @CheckForNull EAVideoPacket.VLC0 vlc) + public @Nonnull SectorEAVideo readSectorPackets(@Nonnull CdSector sector, @CheckForNull EAVideoPacket.VLC0 vlc) throws BinaryDataNotRecognized { if (_blnEnd) @@ -70,18 +73,19 @@ public boolean isEnd() { try { ByteArrayFPIS is = sector.getCdUserDataStream(); - if (iSkip > 0) - is.skip(iSkip); + if (vlc != null) { + is.skip(EAVideoPacket.VLC0.SIZEOF); + } _sectorStream.addStream(is, sector); - return doReadSectorPackets(sector, vlc); + return readSectorPacketsThrowsIOEx(sector, vlc); } catch (IOException ex) { throw new RuntimeException("Should not happen", ex); } } - private @Nonnull SectorEAVideo doReadSectorPackets(@Nonnull CdSector sector, @CheckForNull EAVideoPacket.VLC0 vlc) + private @Nonnull SectorEAVideo readSectorPacketsThrowsIOEx(@Nonnull CdSector sector, @CheckForNull EAVideoPacket.VLC0 vlc) throws BinaryDataNotRecognized, IOException { @@ -92,17 +96,30 @@ public boolean isEnd() { } while (true) { + if (_header == null) { - if (_sectorStream.available() < EAVideoPacket.Header.SIZEOF) - break; - _iCurrentPacketStartSector = _sectorStream.getCurrentMeta().getSectorIndexFromStart(); - _header = EAVideoPacket.Header.read(_sectorStream); - } else if (_header.isEndPacket()) { - // end of stream - _blnEnd = true; - break; + if (_headerType == null) { + // enough data to read the header type? + if (_sectorStream.available() < EAVideoPacket.Type.SIZEOF) + break; + _iCurrentPacketStartSector = _sectorStream.getCurrentMeta().getSectorIndexFromStart(); + _headerType = EAVideoPacket.readHeaderType(_sectorStream); + + // end of stream encountered? + if (_headerType == EAVideoPacket.Type.ZEROES) { + _blnEnd = true; + break; + } + } else { + // enough data to read the header size? + if (_sectorStream.available() < _headerType.bytesNeededToFinishHeader()) + break; + _header = _headerType.readHeader(_sectorStream); + _headerType = null; + } } else { + // enough data to read the header payload? if (_sectorStream.available() < _header.getPayloadSize()) break; diff --git a/jpsxdec/src/jpsxdec/modules/eavideo/SectorClaimToEAVideo.java b/jpsxdec/src/jpsxdec/modules/eavideo/SectorClaimToEAVideo.java index 0df6d9d..eefb41f 100644 --- a/jpsxdec/src/jpsxdec/modules/eavideo/SectorClaimToEAVideo.java +++ b/jpsxdec/src/jpsxdec/modules/eavideo/SectorClaimToEAVideo.java @@ -97,25 +97,26 @@ public void sectorRead(@Nonnull SectorClaimSystem.ClaimableSector cs, if (lngMagic == EAVideoPacket.MAGIC_VLC0) { // we've found a header, now make sure the whole VLC packet is valid - EAVideoPacket.VLC0 vlc; + EAVideoPacket.VLC0 vlcPacket; try { - vlc = EAVideoPacket.readVlc0(cdSector.getCdUserDataStream()); + vlcPacket = EAVideoPacket.readVlc0(cdSector.getCdUserDataStream()); } catch (IOException ex) { throw new RuntimeException("Should not happen"); } - if (vlc != null) { + if (vlcPacket != null) { // new video - _sectorStream = new EAVideoStreamReader(); - rrSector = _sectorStream.readSectorPackets(cdSector, EAVideoPacket.VLC0.SIZEOF, vlc); // tell listener to end any existing videos if (_listener != null) _listener.endVideo(log); + + _sectorStream = new EAVideoStreamReader(); + rrSector = _sectorStream.readSectorPackets(cdSector, vlcPacket); } } } else { // add to existing stream - rrSector = _sectorStream.readSectorPackets(cdSector, 0, null); + rrSector = _sectorStream.readSectorPackets(cdSector, null); } if (rrSector != null) { diff --git a/jpsxdec/src/jpsxdec/util/PushAvailableInputStream.java b/jpsxdec/src/jpsxdec/util/PushAvailableInputStream.java index 5ff699a..a5432cf 100644 --- a/jpsxdec/src/jpsxdec/util/PushAvailableInputStream.java +++ b/jpsxdec/src/jpsxdec/util/PushAvailableInputStream.java @@ -71,10 +71,15 @@ public Pair(@Nonnull InputStream is, @Nonnull T meta) { private int _iPendingAvailable = 0; public void addStream(@Nonnull InputStream is, @Nonnull META meta) throws IOException { - if (!_pieces.isEmpty()) + // remove the current head if it is empty, and skip any additional empty pieces + while (!_pieces.isEmpty() && _pieces.peek().is.available() == 0) + _pieces.remove(); + + if (!_pieces.isEmpty()) { // if the queue is empty, don't add to the pending available - // its available cound will be in the stream itself - _iPendingAvailable += is.available(); + // its available count will be in the stream itself + _iPendingAvailable += is.available(); + } _pieces.add(new Pair(is, meta)); }