Skip to content

Commit

Permalink
Merge pull request #304 from LossyDragon/binary-reader-fix
Browse files Browse the repository at this point in the history
Add compat methods for ByteArrayOutputStream and InputStream
  • Loading branch information
LossyDragon authored Dec 31, 2024
2 parents 7290ebe + 11a33cb commit f380de3
Show file tree
Hide file tree
Showing 12 changed files with 377 additions and 15 deletions.
5 changes: 3 additions & 2 deletions src/main/java/in/dragonbra/javasteam/steam/cdn/Client.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import `in`.dragonbra.javasteam.types.ChunkData
import `in`.dragonbra.javasteam.types.DepotManifest
import `in`.dragonbra.javasteam.util.SteamKitWebRequestException
import `in`.dragonbra.javasteam.util.Strings
import `in`.dragonbra.javasteam.util.compat.readNBytesCompat
import `in`.dragonbra.javasteam.util.log.LogManager
import `in`.dragonbra.javasteam.util.log.Logger
import `in`.dragonbra.javasteam.util.stream.MemoryStream
Expand Down Expand Up @@ -257,7 +258,7 @@ class Client(steamClient: SteamClient) : Closeable {
if (depotKey == null) {
val bytesRead = withTimeout(responseBodyTimeout) {
response.body.byteStream().use { input ->
input.readNBytes(destination, 0, contentLength)
input.readNBytesCompat(destination, 0, contentLength)
}
}

Expand All @@ -274,7 +275,7 @@ class Client(steamClient: SteamClient) : Closeable {
try {
val bytesRead = withTimeout(responseBodyTimeout) {
response.body.byteStream().use { input ->
input.readNBytes(buffer, 0, contentLength)
input.readNBytesCompat(buffer, 0, contentLength)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import `in`.dragonbra.javasteam.types.KeyValue
import `in`.dragonbra.javasteam.util.SteamKitWebRequestException
import `in`.dragonbra.javasteam.util.Strings
import `in`.dragonbra.javasteam.util.Utils
import `in`.dragonbra.javasteam.util.compat.readNBytesCompat
import `in`.dragonbra.javasteam.util.log.LogManager
import `in`.dragonbra.javasteam.util.log.Logger
import kotlinx.coroutines.CancellationException
Expand Down Expand Up @@ -419,7 +420,7 @@ class ContentDownloader(val steamClient: SteamClient) {
fsOld.channel.position(match.oldChunk.offset)

val tmp = ByteArray(match.oldChunk.uncompressedLength)
fsOld.readNBytes(tmp, 0, tmp.size)
fsOld.readNBytesCompat(tmp, 0, tmp.size)

val adler = Utils.adlerHash(tmp)
if (adler != match.oldChunk.checksum) {
Expand All @@ -441,7 +442,7 @@ class ContentDownloader(val steamClient: SteamClient) {
fsOld.channel.position(match.oldChunk.offset)

val tmp = ByteArray(match.oldChunk.uncompressedLength)
fsOld.readNBytes(tmp, 0, tmp.size)
fsOld.readNBytesCompat(tmp, 0, tmp.size)

fs.channel.position(match.newChunk.offset)
fs.write(tmp)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package `in`.dragonbra.javasteam.steam.contentdownloader

import `in`.dragonbra.javasteam.types.DepotManifest
import `in`.dragonbra.javasteam.util.compat.readNBytesCompat
import `in`.dragonbra.javasteam.util.log.LogManager
import `in`.dragonbra.javasteam.util.log.Logger
import java.io.ByteArrayOutputStream
Expand Down Expand Up @@ -70,7 +71,7 @@ class FileManifestProvider(private val file: Path) : IManifestProvider {
if (!entry.isDirectory) {
val entryBytes = ByteArray(entry.size.toInt())

from.readNBytes(entryBytes, 0, entryBytes.size)
from.readNBytesCompat(entryBytes, 0, entryBytes.size)
to.write(entryBytes)
}

Expand Down Expand Up @@ -126,7 +127,7 @@ class FileManifestProvider(private val file: Path) : IManifestProvider {
ZipInputStream(fis).use { zip ->
seekToEntry(zip, getLatestEntryName(depotID))?.let { idEntry ->
if (idEntry.size > 0) {
ByteBuffer.wrap(zip.readNBytes(idEntry.size.toInt())).getLong()
ByteBuffer.wrap(zip.readNBytesCompat(idEntry.size.toInt())).getLong()
} else {
null
}
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/in/dragonbra/javasteam/types/DepotManifest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import `in`.dragonbra.javasteam.protobufs.steamclient.ContentManifest.ContentMan
import `in`.dragonbra.javasteam.protobufs.steamclient.ContentManifest.ContentManifestPayload
import `in`.dragonbra.javasteam.protobufs.steamclient.ContentManifest.ContentManifestSignature
import `in`.dragonbra.javasteam.util.Utils
import `in`.dragonbra.javasteam.util.compat.readNBytesCompat
import `in`.dragonbra.javasteam.util.crypto.CryptoHelper
import `in`.dragonbra.javasteam.util.log.LogManager
import `in`.dragonbra.javasteam.util.log.Logger
Expand Down Expand Up @@ -249,17 +250,17 @@ class DepotManifest {

PROTOBUF_PAYLOAD_MAGIC -> {
val payloadLength = br.readInt()
payload = ContentManifestPayload.parseFrom(stream.readNBytes(payloadLength))
payload = ContentManifestPayload.parseFrom(stream.readNBytesCompat(payloadLength))
}

PROTOBUF_METADATA_MAGIC -> {
val metadataLength = br.readInt()
metadata = ContentManifestMetadata.parseFrom(stream.readNBytes(metadataLength))
metadata = ContentManifestMetadata.parseFrom(stream.readNBytesCompat(metadataLength))
}

PROTOBUF_SIGNATURE_MAGIC -> {
val signatureLength = br.readInt()
signature = ContentManifestSignature.parseFrom(stream.readNBytes(signatureLength))
signature = ContentManifestSignature.parseFrom(stream.readNBytesCompat(signatureLength))
}

else -> throw NoSuchElementException("Unrecognized magic value ${magic.toHexString(HexFormat.Default)} in depot manifest.")
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/in/dragonbra/javasteam/types/Steam3Manifest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package `in`.dragonbra.javasteam.types

import `in`.dragonbra.javasteam.enums.EDepotFileFlag
import `in`.dragonbra.javasteam.util.compat.readNBytesCompat
import `in`.dragonbra.javasteam.util.stream.BinaryReader
import java.time.Instant
import java.util.Date
Expand Down Expand Up @@ -48,7 +49,7 @@ class Steam3Manifest(
) {
companion object {
internal fun deserialize(ds: BinaryReader): Chunk = Chunk(
chunkGID = ds.readNBytes(20),
chunkGID = ds.readNBytesCompat(20),
checksum = ds.readInt(),
offset = ds.readLong(),
decompressedSize = ds.readInt(),
Expand All @@ -62,8 +63,8 @@ class Steam3Manifest(
val fileName = ds.readNullTermString(Charsets.UTF_8)
val totalSize = ds.readLong()
val flags = EDepotFileFlag.from(ds.readInt())
val hashContent = ds.readNBytes(20)
val hashFileName = ds.readNBytes(20)
val hashContent = ds.readNBytesCompat(20)
val hashFileName = ds.readNBytesCompat(20)
val numChunks = ds.readInt()

return FileMapping(
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/in/dragonbra/javasteam/util/VZipUtil.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package `in`.dragonbra.javasteam.util

import `in`.dragonbra.javasteam.util.compat.readNBytesCompat
import `in`.dragonbra.javasteam.util.crypto.CryptoHelper
import `in`.dragonbra.javasteam.util.stream.BinaryReader
import `in`.dragonbra.javasteam.util.stream.BinaryWriter
Expand Down Expand Up @@ -69,7 +70,7 @@ object VZipUtil {
dictionarySize,
windowBuffer
).use { lzmaInput ->
lzmaInput.readNBytes(destination, 0, sizeDecompressed)
lzmaInput.readNBytesCompat(destination, 0, sizeDecompressed)
}

if (verifyChecksum && Utils.crc32(destination).toInt() != outputCrc) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/in/dragonbra/javasteam/util/ZipUtil.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package `in`.dragonbra.javasteam.util

import `in`.dragonbra.javasteam.util.compat.readNBytesCompat
import `in`.dragonbra.javasteam.util.stream.MemoryStream
import java.util.zip.ZipInputStream

Expand All @@ -16,7 +17,7 @@ object ZipUtil {
throw IllegalArgumentException("The destination buffer is smaller than the decompressed data size.")
}

val bytesRead = zip.readNBytes(destination, 0, sizeDecompressed)
val bytesRead = zip.readNBytesCompat(destination, 0, sizeDecompressed)

if (zip.nextEntry != null) {
throw IllegalArgumentException("Given stream should only contain one zip entry")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package `in`.dragonbra.javasteam.util.compat

import java.io.ByteArrayOutputStream

/**
* Compatibility class to provide compatibility with Java ByteArrayOutputStream.
*/
object ByteArrayOutputStreamCompat {

@JvmStatic
fun toString(byteArrayOutputStream: ByteArrayOutputStream): String =
String(byteArrayOutputStream.toByteArray(), 0, byteArrayOutputStream.size())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package `in`.dragonbra.javasteam.util.compat

import java.io.IOException
import java.io.InputStream
import java.util.Objects
import kotlin.jvm.Throws
import kotlin.math.min

/**
* Compatibility (extension) functions for [InputStream.readNBytes].
* These are basically the same from InputStream.
*/

@Throws(IOException::class)
fun InputStream.readNBytesCompat(b: ByteArray, off: Int, len: Int): Int {
Objects.checkFromIndexSize(off, len, b.size)

var n = 0

while (n < len) {
val count = read(b, off + n, len - n)
if (count < 0) {
break
}
n += count
}

return n
}

@Suppress("RedundantExplicitType")
@Throws(IOException::class)
fun InputStream.readNBytesCompat(len: Int): ByteArray {
if (len < 0) {
throw IllegalArgumentException("len < 0")
}

var bufs: MutableList<ByteArray>? = null
var result: ByteArray? = null
var total: Int = 0
var remaining: Int = len
var n: Int

do {
var buf = ByteArray(min(remaining, 8192))
var nread = 0

// read to EOF which may read more or less than buffer size
while (read(buf, nread, minOf(buf.size - nread, remaining)).also { n = it } > 0) {
nread += n
remaining -= n
}

if (nread > 0) {
if ((Integer.MAX_VALUE - 8) - total < nread) {
throw OutOfMemoryError("Required array size too large")
}
total += nread
if (result == null) {
result = buf
} else {
if (bufs == null) {
bufs = arrayListOf()
bufs.add(result)
}
bufs.add(buf)
}
}
// if the last call to read returned -1 or the number of bytes
// requested have been read then break
} while (n >= 0 && remaining > 0)

if (bufs == null) {
if (result == null) {
return ByteArray(0)
}
return if (result.size == total) result else result.copyOf(total)
}

result = ByteArray(total)
var offset = 0
remaining = total

bufs.forEach { b ->
var count = min(b.size, remaining)
System.arraycopy(b, 0, result, offset, count)
offset += count
remaining -= count
}

return result
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package in.dragonbra.javasteam.util.stream;

import in.dragonbra.javasteam.util.compat.ByteArrayOutputStreamCompat;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -129,6 +131,7 @@ public String readNullTermString(Charset charset) throws IOException {

byte[] bytes = buffer.toByteArray();
position += bytes.length;

return new String(bytes, charset);
}

Expand All @@ -146,7 +149,7 @@ private String readNullTermUtf8String() throws IOException {

position++; // Increment for the null terminator

return baos.toString(StandardCharsets.UTF_8);
return ByteArrayOutputStreamCompat.toString(baos);
}

public int getPosition() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package in.dragonbra.javasteam.util.compat;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;

public class ByteArrayOutputStreamCompatTest {

@Test
public void testEmptyStream() {
var baos = new ByteArrayOutputStream();

var compatResult = ByteArrayOutputStreamCompat.toString(baos);
var standardResult = baos.toString();

Assertions.assertEquals("", compatResult);
Assertions.assertEquals("", standardResult);
Assertions.assertEquals(standardResult, compatResult);
}

@Test
public void testAsciiContent() {
var baos = new ByteArrayOutputStream();
var testString = "Hello, World!";

baos.write(testString.getBytes(StandardCharsets.UTF_8), 0, testString.length());

var compatResult = ByteArrayOutputStreamCompat.toString(baos);
var standardResult = baos.toString();

Assertions.assertEquals(testString, compatResult);
Assertions.assertEquals(testString, standardResult);
Assertions.assertEquals(standardResult, compatResult);
}

@Test
public void testUnicodeContent() {
var baos = new ByteArrayOutputStream();
var testString = "Hello, 世界! 👋";
var bytes = testString.getBytes(StandardCharsets.UTF_8);

baos.write(bytes, 0, bytes.length);

var compatResult = ByteArrayOutputStreamCompat.toString(baos);
var standardResult = baos.toString();

Assertions.assertEquals(testString, compatResult);
Assertions.assertEquals(testString, standardResult);
Assertions.assertEquals(standardResult, compatResult);
}

@Test
public void testLargeContent() {
var baos = new ByteArrayOutputStream();
var largeString = new StringBuilder();
for (int i = 0; i < 1000; i++) {
largeString.append("Line ").append(i).append("\n");
}
var testString = largeString.toString();
var bytes = testString.getBytes(StandardCharsets.UTF_8);
baos.write(bytes, 0, bytes.length);

var compatResult = ByteArrayOutputStreamCompat.toString(baos);
var standardResult = baos.toString();

Assertions.assertEquals(testString, compatResult);
Assertions.assertEquals(testString, standardResult);
Assertions.assertEquals(standardResult, compatResult);
}

@Test
public void testPartialWrites() {
var baos = new ByteArrayOutputStream();
var part1 = "Hello";
var part2 = ", ";
var part3 = "World!";

baos.write(part1.getBytes(StandardCharsets.UTF_8), 0, part1.length());
baos.write(part2.getBytes(StandardCharsets.UTF_8), 0, part2.length());
baos.write(part3.getBytes(StandardCharsets.UTF_8), 0, part3.length());

var expected = part1 + part2 + part3;
var compatResult = ByteArrayOutputStreamCompat.toString(baos);
var standardResult = baos.toString();

Assertions.assertEquals(expected, compatResult);
Assertions.assertEquals(expected, standardResult);
Assertions.assertEquals(standardResult, compatResult);
}

}
Loading

0 comments on commit f380de3

Please sign in to comment.