Skip to content

Commit a0bce84

Browse files
committed
start interface support
1 parent 2ef9193 commit a0bce84

6 files changed

Lines changed: 705 additions & 41 deletions

File tree

.idea/workspace.xml

Lines changed: 29 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/kotlin/dev/openrune/cache/diff/CacheBinaryFormat.kt

Lines changed: 231 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ object CacheBinaryFormat {
3434

3535
private const val MAGIC = "ORCA"
3636
private const val SPRITE_SHA_LEN = 32
37+
private const val TRAILER_SPRITE_META = "SMET"
38+
private const val TRAILER_SPRITE_RASTER = "SMRP"
39+
private const val TRAILER_OPENRS2_ID = "OCID"
40+
private const val TRAILER_INTERFACE_MANIFEST = "IFMF"
41+
private const val TRAILER_CLIENT_SCRIPTS = "CSRB"
3742

3843
private val gson = Gson()
3944

@@ -43,6 +48,19 @@ object CacheBinaryFormat {
4348
val sub: Map<Int, String> = emptyMap(),
4449
)
4550

51+
data class IndexedSpriteMeta(
52+
val offsetX: Int = 0,
53+
val offsetY: Int = 0,
54+
val width: Int = 0,
55+
val height: Int = 0,
56+
val averageColor: Int = -1,
57+
val subHeight: Int = 0,
58+
val subWidth: Int = 0,
59+
val alphaBase64: String? = null,
60+
val rasterBase64: String = "",
61+
val palette: List<Int> = emptyList(),
62+
)
63+
4664
data class DecodedRev(
4765
val revision: Int,
4866
val openRs2CacheId: Long? = null,
@@ -51,9 +69,12 @@ object CacheBinaryFormat {
5169
val gameval: Map<String, Map<Int, GamevalExtra>> = emptyMap(),
5270
val sprites: Map<Int, ByteArray> = emptyMap(),
5371
val spriteSha256: Map<Int, ByteArray> = emptyMap(),
72+
val spriteMetadata: Map<Int, List<IndexedSpriteMeta>> = emptyMap(),
5473
val mapObjects: Map<Int, List<LocationCustom>> = emptyMap(),
5574
val mapRegions: Map<Int, RegionData> = emptyMap(),
5675
val xteasByRegion: Map<Int, IntArray> = emptyMap(),
76+
val interfaceManifest: List<InterfaceManifestEntry> = emptyList(),
77+
val clientScripts: Map<Int, ByteArray> = emptyMap(),
5778
)
5879

5980
fun encode(
@@ -63,9 +84,12 @@ object CacheBinaryFormat {
6384
configs: Map<String, Map<Int, DefinitionSnapshot>>,
6485
gameval: Map<String, Map<Int, GamevalExtra>> = emptyMap(),
6586
sprites: Map<Int, ByteArray> = emptyMap(),
87+
spriteMetadata: Map<Int, List<IndexedSpriteMeta>> = emptyMap(),
6688
mapObjects: Map<Int, List<LocationCustom>> = emptyMap(),
6789
mapRegions: Map<Int, RegionData> = emptyMap(),
6890
xteasByRegion: Map<Int, IntArray> = emptyMap(),
91+
interfaceManifest: List<InterfaceManifestEntry> = emptyList(),
92+
clientScripts: Map<Int, ByteArray> = emptyMap(),
6993
): ByteArray {
7094
val body = ByteArrayOutputStream()
7195
val configTypes = ConfigDiffType.diffTypeNames
@@ -175,11 +199,74 @@ object CacheBinaryFormat {
175199
repeat(4) { writeInt32LE(body, norm[it]) }
176200
}
177201

178-
// Optional source OpenRS2 cache id trailer for boot-time freshness checks.
202+
// Optional trailers (marker-prefixed for backward compatibility).
203+
body.write(TRAILER_SPRITE_META.toByteArray(Charsets.UTF_8))
204+
writeVarint(body, spriteMetadata.size)
205+
spriteMetadata.entries.sortedBy { it.key }.forEach { (id, metas) ->
206+
writeVarint(body, id)
207+
writeVarint(body, metas.size)
208+
metas.forEach { meta ->
209+
writeSVarint(body, meta.offsetX)
210+
writeSVarint(body, meta.offsetY)
211+
writeVarint(body, meta.width)
212+
writeVarint(body, meta.height)
213+
writeSVarint(body, meta.averageColor)
214+
writeVarint(body, meta.subHeight)
215+
writeVarint(body, meta.subWidth)
216+
val alpha = meta.alphaBase64
217+
if (alpha == null) {
218+
body.write(0)
219+
} else {
220+
body.write(1)
221+
writeString(body, alpha)
222+
}
223+
}
224+
}
225+
226+
// SMRP: raster + palette data (only entries that have raster populated)
227+
val spriteMetaWithRaster = spriteMetadata.filterValues { metas -> metas.any { it.rasterBase64.isNotEmpty() } }
228+
if (spriteMetaWithRaster.isNotEmpty()) {
229+
body.write(TRAILER_SPRITE_RASTER.toByteArray(Charsets.UTF_8))
230+
writeVarint(body, spriteMetaWithRaster.size)
231+
spriteMetaWithRaster.entries.sortedBy { it.key }.forEach { (id, metas) ->
232+
writeVarint(body, id)
233+
writeVarint(body, metas.size)
234+
metas.forEach { meta ->
235+
writeString(body, meta.rasterBase64)
236+
writeVarint(body, meta.palette.size)
237+
meta.palette.forEach { writeInt32LE(body, it) }
238+
}
239+
}
240+
}
241+
179242
if (openRs2CacheId != null) {
243+
body.write(TRAILER_OPENRS2_ID.toByteArray(Charsets.UTF_8))
180244
writeInt64LE(body, openRs2CacheId)
181245
}
182246

247+
body.write(TRAILER_INTERFACE_MANIFEST.toByteArray(Charsets.UTF_8))
248+
writeVarint(body, interfaceManifest.size)
249+
interfaceManifest.sortedBy { it.interfaceId }.forEach { entry ->
250+
writeVarint(body, entry.interfaceId)
251+
body.write(if (entry.gameval != null) 1 else 0)
252+
if (entry.gameval != null) writeString(body, entry.gameval)
253+
body.write(
254+
when (entry.iflegacy) {
255+
true -> 1
256+
false -> 0
257+
null -> 2
258+
}
259+
)
260+
}
261+
262+
body.write(TRAILER_CLIENT_SCRIPTS.toByteArray(Charsets.UTF_8))
263+
writeVarint(body, clientScripts.size)
264+
clientScripts.entries.sortedBy { it.key }.forEach { (scriptId, raw) ->
265+
writeVarint(body, scriptId)
266+
writeVarint(body, raw.size)
267+
body.write(raw)
268+
}
269+
183270
val bodyBytes = body.toByteArray()
184271
val compressed = Zstd.compress(bodyBytes)
185272
val header = ByteBuffer.allocate(4 + 4 + 4).order(ByteOrder.LITTLE_ENDIAN)
@@ -309,7 +396,129 @@ object CacheBinaryFormat {
309396
xteasByRegion[sq] = IntArray(4) { readInt32LE(input) }
310397
}
311398

312-
val openRs2CacheId = if (input.available() >= 8) readInt64LE(input) else null
399+
val spriteMetadata = HashMap<Int, List<IndexedSpriteMeta>>()
400+
val interfaceManifest = ArrayList<InterfaceManifestEntry>()
401+
val clientScripts = HashMap<Int, ByteArray>()
402+
var openRs2CacheId: Long? = null
403+
if (input.available() > 0) {
404+
val trailer = readBytes(input, input.available())
405+
val trailerInput = ByteArrayInputStream(trailer)
406+
var parsedWithMarkers = false
407+
408+
while (trailerInput.available() >= 4) {
409+
val marker = String(readBytes(trailerInput, 4), Charsets.UTF_8)
410+
when (marker) {
411+
TRAILER_SPRITE_META -> {
412+
parsedWithMarkers = true
413+
val idCount = readVarint(trailerInput)
414+
repeat(idCount) {
415+
val id = readVarint(trailerInput)
416+
val count = readVarint(trailerInput)
417+
val metas = ArrayList<IndexedSpriteMeta>(count)
418+
repeat(count) {
419+
val offsetX = readSVarint(trailerInput)
420+
val offsetY = readSVarint(trailerInput)
421+
val width = readVarint(trailerInput)
422+
val height = readVarint(trailerInput)
423+
val averageColor = readSVarint(trailerInput)
424+
val subHeight = readVarint(trailerInput)
425+
val subWidth = readVarint(trailerInput)
426+
val hasAlpha = trailerInput.read() == 1
427+
val alphaBase64 = if (hasAlpha) readString(trailerInput) else null
428+
metas.add(
429+
IndexedSpriteMeta(
430+
offsetX = offsetX,
431+
offsetY = offsetY,
432+
width = width,
433+
height = height,
434+
averageColor = averageColor,
435+
subHeight = subHeight,
436+
subWidth = subWidth,
437+
alphaBase64 = alphaBase64,
438+
)
439+
)
440+
}
441+
spriteMetadata[id] = metas
442+
}
443+
}
444+
TRAILER_SPRITE_RASTER -> {
445+
parsedWithMarkers = true
446+
val idCount = readVarint(trailerInput)
447+
repeat(idCount) {
448+
val id = readVarint(trailerInput)
449+
val count = readVarint(trailerInput)
450+
val existingMetas = spriteMetadata[id]?.toMutableList()
451+
if (existingMetas == null) {
452+
// No SMET entry — skip raster data
453+
repeat(count) {
454+
readString(trailerInput)
455+
val palSize = readVarint(trailerInput)
456+
repeat(palSize) { readInt32LE(trailerInput) }
457+
}
458+
return@repeat
459+
}
460+
val updated = ArrayList<IndexedSpriteMeta>(count)
461+
repeat(count) { idx ->
462+
val rasterBase64 = readString(trailerInput)
463+
val palSize = readVarint(trailerInput)
464+
val palette = List(palSize) { readInt32LE(trailerInput) }
465+
val base = existingMetas.getOrNull(idx) ?: IndexedSpriteMeta()
466+
updated.add(base.copy(rasterBase64 = rasterBase64, palette = palette))
467+
}
468+
spriteMetadata[id] = updated
469+
}
470+
}
471+
TRAILER_OPENRS2_ID -> {
472+
parsedWithMarkers = true
473+
if (trailerInput.available() >= 8) {
474+
openRs2CacheId = readInt64LE(trailerInput)
475+
}
476+
}
477+
TRAILER_INTERFACE_MANIFEST -> {
478+
parsedWithMarkers = true
479+
val count = readVarint(trailerInput)
480+
repeat(count) {
481+
val interfaceId = readVarint(trailerInput)
482+
val hasGameval = trailerInput.read() == 1
483+
val gameval = if (hasGameval) readString(trailerInput) else null
484+
val legacyFlag = trailerInput.read()
485+
val iflegacy = when (legacyFlag) {
486+
1 -> true
487+
0 -> false
488+
else -> null
489+
}
490+
interfaceManifest.add(
491+
InterfaceManifestEntry(
492+
interfaceId = interfaceId,
493+
gameval = gameval,
494+
iflegacy = iflegacy,
495+
)
496+
)
497+
}
498+
}
499+
TRAILER_CLIENT_SCRIPTS -> {
500+
parsedWithMarkers = true
501+
val count = readVarint(trailerInput)
502+
repeat(count) {
503+
val scriptId = readVarint(trailerInput)
504+
val len = readVarint(trailerInput)
505+
clientScripts[scriptId] = readBytes(trailerInput, len)
506+
}
507+
}
508+
else -> {
509+
// Backward compatibility: old binaries had an optional raw 8-byte OpenRS2 id trailer.
510+
if (!parsedWithMarkers && trailer.size >= 8) {
511+
openRs2CacheId = readInt64LE(ByteArrayInputStream(trailer.copyOfRange(0, 8)))
512+
}
513+
break
514+
}
515+
}
516+
}
517+
518+
if (!parsedWithMarkers && openRs2CacheId == null && trailer.size >= 8) {
519+
openRs2CacheId = readInt64LE(ByteArrayInputStream(trailer.copyOfRange(0, 8)))
520+
}
521+
}
313522

314523
return DecodedRev(
315524
revision = revision,
@@ -319,9 +528,12 @@ object CacheBinaryFormat {
319528
gameval = gameval,
320529
sprites = sprites,
321530
spriteSha256 = spriteSha256,
531+
spriteMetadata = spriteMetadata,
322532
mapObjects = mapObjects,
323533
mapRegions = mapRegions,
324534
xteasByRegion = xteasByRegion,
535+
interfaceManifest = interfaceManifest,
536+
clientScripts = clientScripts,
325537
)
326538
}
327539

@@ -333,13 +545,29 @@ object CacheBinaryFormat {
333545
configs: Map<String, Map<Int, DefinitionSnapshot>>,
334546
gameval: Map<String, Map<Int, GamevalExtra>> = emptyMap(),
335547
sprites: Map<Int, ByteArray> = emptyMap(),
548+
spriteMetadata: Map<Int, List<IndexedSpriteMeta>> = emptyMap(),
336549
mapObjects: Map<Int, List<LocationCustom>> = emptyMap(),
337550
mapRegions: Map<Int, RegionData> = emptyMap(),
338551
xteasByRegion: Map<Int, IntArray> = emptyMap(),
552+
interfaceManifest: List<InterfaceManifestEntry> = emptyList(),
553+
clientScripts: Map<Int, ByteArray> = emptyMap(),
339554
) {
340555
file.parentFile?.mkdirs()
341556
file.writeBytes(
342-
encode(revision, openRs2CacheId, manifest, configs, gameval, sprites, mapObjects, mapRegions, xteasByRegion)
557+
encode(
558+
revision,
559+
openRs2CacheId,
560+
manifest,
561+
configs,
562+
gameval,
563+
sprites,
564+
spriteMetadata,
565+
mapObjects,
566+
mapRegions,
567+
xteasByRegion,
568+
interfaceManifest,
569+
clientScripts,
570+
)
343571
)
344572
}
345573

0 commit comments

Comments
 (0)