Skip to content
Closed

d #152

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
4 changes: 4 additions & 0 deletions .data/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/*
symbols/.local/
!gamevals/
!gamevals-binary/
!raw-cache/
!symbols/
!.gitignore

gamevals-binary/*
!gamevals-binary/max-ids.toml
22 changes: 22 additions & 0 deletions .data/gamevals-binary/max-ids.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# OSRS cache gameval max IDs per table.
# IDs 0..=max are reserved; custom gamevals must be > max.
# Generated by freshCache / GamevalDumper - do not hand-edit.
# dbcol is omitted (auto-generated from table/column defs).

revision = 239

[max_ids]
dbrow = 16939
dbtable = 258
interface = 968
inv = 1027
jingle = 1196
loc = 62399
npc = 16337
obj = 34058
seq = 14474
spotanim = 4017
sprites = 8559
varbit = 20396
varcs = 1506
varp = 5724
114 changes: 114 additions & 0 deletions .github/workflows/gameval-conflicts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: Gameval Conflict Check

# Runs on every PR so it can be a required status check (blocks merge until it completes).
# Auto-fixes conflicting gamevals and pushes — no environment approval.
# Comments on the PR only when conflicts were found/fixed; silent when clean.

on:
pull_request:
types: [opened, synchronize, reopened]

concurrency:
group: gameval-conflicts-${{ github.event.pull_request.number }}
cancel-in-progress: true

permissions:
contents: write
checks: write
pull-requests: write

jobs:
gameval-conflicts:
runs-on: ubuntu-latest
# No environment: — do not gate fixes behind approval.

steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Fix conflicting gamevals
id: resolve
run: |
set +e
python3 tools/scripts/fix_gameval_conflicts.py | tee /tmp/gameval-fix-log.txt
code=${PIPESTATUS[0]}
set -e
if [ "$code" -eq 0 ]; then
echo "fixed=false" >> "$GITHUB_OUTPUT"
elif [ "$code" -eq 2 ]; then
echo "fixed=true" >> "$GITHUB_OUTPUT"
else
exit "$code"
fi

- name: Commit, push, and report conflicts
if: steps.resolve.outputs.fixed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
SAME_REPO="${{ github.event.pull_request.head.repo.full_name == github.repository }}"
if [ "$SAME_REPO" != "true" ]; then
echo "::error::Conflicting gamevals on a fork PR — push fixes to the branch (or open the PR from this repo)."
cat /tmp/gameval-fix-log.txt
{
echo "## Gameval conflicts found"
echo
echo "Could not push fixes (fork PR). Please apply the reassignments below on your branch."
echo
echo '```'
cat /tmp/gameval-fix-log.txt
echo '```'
} > /tmp/gameval-pr-body.md
gh pr comment "${{ github.event.pull_request.number }}" --body-file /tmp/gameval-pr-body.md
exit 1
fi

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

git add -u -- content api .data/gamevals

if git diff --cached --quiet; then
echo "Nothing staged."
exit 0
fi

git commit -m "fix(gamevals): reassign IDs conflicting with OSRS reserved range"
git push origin "HEAD:${{ github.head_ref }}"

# GITHUB_TOKEN pushes do not re-trigger workflows; mark the new head so a
# required "gameval-conflicts" check is not stuck waiting on this SHA.
NEW_SHA="$(git rev-parse HEAD)"
jq -n \
--arg name "Gameval Conflict Check / gameval-conflicts" \
--arg sha "$NEW_SHA" \
--arg summary "$(cat /tmp/gameval-fix-log.txt)" \
'{
name: $name,
head_sha: $sha,
status: "completed",
conclusion: "success",
output: {
title: "Gameval conflicts auto-fixed",
summary: $summary
}
}' | gh api "repos/${{ github.repository }}/check-runs" --input -

{
echo "## Gameval conflicts auto-fixed"
echo
echo "Reserved OSRS IDs come from \`.data/gamevals-binary/max-ids.toml\` (written on \`freshCache\`)."
echo "Custom gamevals must use IDs **greater than** each table's max."
echo
echo "A fix commit was pushed with these reassignments:"
echo
echo '```'
cat /tmp/gameval-fix-log.txt
echo '```'
} > /tmp/gameval-pr-body.md

gh pr comment "${{ github.event.pull_request.number }}" --body-file /tmp/gameval-pr-body.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ ugthanki_kebab=64535
kebab=64534
locust_meat=64533
roe=64532
roedddddddddd=64130
roedddddddddd2=64129
roedddddddddd3=64128
roedddddddddd34=64127
roedddddddddd45=64126
roedddddddddd55=64125
stew=64531
spicy_stew=64530
cooked_rabbit=64529
Expand Down Expand Up @@ -480,4 +486,4 @@ effect_toa_tears_of_elidinis=64136
effect_toa_liquid_adrenaline=64135
effect_toa_smelling_salts=64134
effect_toa_silk_dressing=64133
effect_toa_blessed_crystal_scarab=64132
effect_toa_blessed_crystal_scarab=64132
134 changes: 134 additions & 0 deletions or-cache/src/main/kotlin/dev/openrune/gamevals/GameValMaxIdManifest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package dev.openrune.gamevals

import java.io.DataInputStream
import java.io.File
import java.io.FileInputStream

/**
* Snapshot of the highest OSRS cache gameval ID per table.
*
* IDs in `0..maxId` (inclusive) are reserved for the official cache. Custom gamevals must use
* IDs strictly greater than the table's max.
*
* Written by [GamevalDumper] during `freshCache`; committed so PR CI can detect conflicts without
* the binary `.dat` dumps.
*/
object GameValMaxIdManifest {
const val RELATIVE_PATH = ".data/gamevals-binary/max-ids.toml"

data class Manifest(
val revision: Int?,
val maxIds: Map<String, Int>,
)

fun path(rootDir: File): File = File(rootDir, RELATIVE_PATH)

fun write(
rootDir: File,
maxIds: Map<String, Int>,
revision: Int? = null,
) {
val file = path(rootDir).apply { parentFile?.mkdirs() }
val body =
buildString {
appendLine("# OSRS cache gameval max IDs per table.")
appendLine("# IDs 0..=max are reserved; custom gamevals must be > max.")
appendLine("# Generated by freshCache / GamevalDumper - do not hand-edit.")
appendLine("# dbcol is omitted (auto-generated from table/column defs).")
appendLine()
if (revision != null) {
appendLine("revision = $revision")
appendLine()
}
appendLine("[max_ids]")
maxIds.toSortedMap().forEach { (table, maxId) ->
appendLine("$table = $maxId")
}
}
file.writeText(body)
}

fun load(rootDir: File): Manifest {
val file = path(rootDir)
require(file.isFile) {
"Missing gameval max-id manifesto at ${file.invariantSeparatorsPath}. " +
"Run :or-cache:freshCache to generate it."
}
return parse(file.readText())
}

fun parse(text: String): Manifest {
var revision: Int? = null
val maxIds = linkedMapOf<String, Int>()
var inMaxIds = false

text.lineSequence().forEach { raw ->
val line = raw.trim()
if (line.isEmpty() || line.startsWith("#")) return@forEach
when {
line == "[max_ids]" -> inMaxIds = true
line.startsWith("[") -> inMaxIds = false
line.startsWith("revision") && '=' in line && !inMaxIds -> {
revision = line.substringAfter('=').trim().toIntOrNull()
}
inMaxIds && '=' in line -> {
val key = line.substringBefore('=').trim()
val value =
line.substringAfter('=').trim().toIntOrNull()
?: error("Invalid max id for '$key': $line")
maxIds[key] = value
}
}
}

require(maxIds.isNotEmpty()) { "Gameval max-id manifesto has no [max_ids] entries." }
return Manifest(revision, maxIds)
}

/** Decode max IDs from `gamevals.dat` only (`dbcol` / columns dat is auto-generated). */
fun readMaxIdsFromDats(binaryDir: File): Map<String, Int> {
val maxIds = linkedMapOf<String, Int>()
val dat = File(binaryDir, "gamevals.dat")
require(dat.isFile) { "Missing gamevals.dat under ${binaryDir.invariantSeparatorsPath}" }
mergeMaxIdsFromDat(dat, maxIds)
maxIds.remove("dbcol")
return maxIds
}

fun writeFromDats(
rootDir: File,
revision: Int? = null,
) {
val binaryDir = File(rootDir, ".data/gamevals-binary")
write(rootDir, readMaxIdsFromDats(binaryDir), revision)
}

private fun mergeMaxIdsFromDat(
datFile: File,
maxIds: MutableMap<String, Int>,
) {
DataInputStream(FileInputStream(datFile)).use { input ->
val tableCount = input.readInt()
repeat(tableCount) {
val nameLength = input.readShort().toInt()
val nameBytes = ByteArray(nameLength)
input.readFully(nameBytes)
val tableName = String(nameBytes, Charsets.UTF_8)

val itemCount = input.readInt()
var max = maxIds[tableName] ?: -1
repeat(itemCount) {
val itemLength = input.readShort().toInt()
val itemBytes = ByteArray(itemLength)
input.readFully(itemBytes)
val itemString = String(itemBytes, Charsets.UTF_8)
val id =
itemString.substringAfterLast('=').trim().toIntOrNull()
?: return@repeat
if (id > max) max = id
}
maxIds[tableName] = max
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ object GamevalDumper {
encodeGameValDat(File(outputDir, "gamevals.dat").path, gamevals)

dumpCols(cache, rev)

GameValMaxIdManifest.writeFromDats(File(".."), revision = rev)
}

fun dumpCols(cache: Cache, rev: Int) {
Expand Down
Loading