fix: Paper 26.2 compatibility for SpigotEntityIdProvider - #70

Open
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch
Open

fix: Paper 26.2 compatibility for SpigotEntityIdProvider#70
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch

Conversation

@TWME-TW

@TWME-TWTWME-TW commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Three fixes for the entity ID provider used on Paper 26.2+:

  1. Add reflection guard for UnsafeValues.nextEntityId()

    • Paper 26.2+ removed this method; verify via reflection before calling
    • Falls through to AtomicInteger reflection path when absent
  2. Fix ArrayIndexOutOfBoundsException in getEntityClass()

    • Paper 26.2+ has no version suffix in the CraftServer package name
    • Skip package name parsing entirely for 1.17+ (flattened) servers
  3. Fix IllegalAccessException on legacy entity counter

    • Skip static final fields (e.g. CURRENT_LEVEL)
    • Add findMutableStaticIntField helper with isFinal check
    • Fall back to local AtomicInteger when no mutable field is found
    • Also search for ENTITY_COUNTER in AtomicInteger field names

Summary by CodeRabbit

  • Bug Fixes
    • Improved compatibility with various Spigot/Paper/Minecraft server versions
    • Enhanced entity ID resolution to be more resilient across version variations
    • Added fallback mechanisms for improved stability when certain version-specific features are unavailable

Three fixes for the entity ID provider used on Paper 26.2+:
1. Add reflection guard for UnsafeValues.nextEntityId()
- Paper 26.2+ removed this method; verify via reflection before calling
- Falls through to AtomicInteger reflection path when absent
2. Fix ArrayIndexOutOfBoundsException in getEntityClass()
- Paper 26.2+ has no version suffix in the CraftServer package name
- Skip package name parsing entirely for 1.17+ (flattened) servers
3. Fix IllegalAccessException on legacy entity counter
- Skip static final fields (e.g. CURRENT_LEVEL)
- Add findMutableStaticIntField helper with isFinal check
- Fall back to local AtomicInteger when no mutable field is found
- Also search for ENTITY_COUNTER in AtomicInteger field names
@coderabbitai

coderabbitaiBot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

SpigotEntityIdProvider gains several defensive layers for entity-ID resolution: the Paper/1.16+ path now guards against missing nextEntityId via reflection, resolveAtomicSupplier adds "ENTITY_COUNTER" as a candidate name, resolveLegacySupplier falls back to a local AtomicInteger instead of throwing, a new findMutableStaticIntField helper performs flexible field scanning, and getEntityClass handles both flattened and pre-1.17 NMS package structures more defensively.

Changes

SpigotEntityIdProvider Resilience

Layer / File(s)Summary
Paper/1.16+ detection guard and atomic candidate extension
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
detectIdSupplier now checks for UnsafeValues.nextEntityId via reflection and falls through to the next strategy on NoSuchMethodException; resolveAtomicSupplier adds "ENTITY_COUNTER" to its candidate field name list.
Legacy supplier fallback and findMutableStaticIntField helper
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
resolveLegacySupplier replaces the hard throw with a local AtomicInteger fallback seeded near Integer.MAX_VALUE; the new findMutableStaticIntField method tries known field names then scans all non-final static int fields, returning null when none match.
Defensive NMS Entity class resolution
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
getEntityClass wraps ClassNotFoundException into IllegalStateException for the 1.17+ flattened path, and computes the pre-1.17 versioned package string with a safe fallback when server package parts are too short.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Tofaa2/EntityLib#45: Introduced the original detectIdSupplier, resolveAtomicSupplier, and resolveLegacySupplier logic that this PR directly extends and hardens.

Suggested reviewers

  • Tofaa2

Poem

🐇 Hop, hop through the NMS maze,
No more crashes on unknown field days!
ENTITY_COUNTER joins the hunt,
Fallback counters bear the brunt.
The bunny found each edge case true —
Defensive code, good as new! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title directly addresses the main change: fixing Paper 26.2 compatibility for SpigotEntityIdProvider, which is the primary focus of the changeset.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`:
- Around line 110-113: The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.
- Around line 142-150: The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92fe61a6-c265-47d5-b654-c3e8a5b4b550

📥 Commits

Reviewing files that changed from the base of the PR and between e797eee and 7a45be1.

📒 Files selected for processing (1)
  • platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Comment on lines +110 to +113
// Last resort: local high-offset counter. Entity ID collision is unlikely
// since the server allocates from 1 upward.
final AtomicInteger fallback = new AtomicInteger(Integer.MAX_VALUE - 100000);
return fallback::incrementAndGet;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fallback counter overflows into negative IDs after ~100k entities.

Seeding at Integer.MAX_VALUE - 100000 means incrementAndGet reaches Integer.MAX_VALUE after ~100k calls and then wraps to Integer.MIN_VALUE, producing negative entity IDs that can collide or misbehave on clients. Consider seeding lower (well above the server's allocation range but far from overflow), or documenting/bounding the wrap behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 110 - 113, The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.

Comment on lines +142 to +150
// Wildcard fallback: any non-final static int field
for (final Field field : clazz.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())
&& field.getType() == Integer.TYPE
&& !Modifier.isFinal(field.getModifiers())) {
return field;
}
}
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Inspect how the resolved legacy field is mutated
rg -nP -C3 '\bsetInt\s*\(' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 552


🏁 Script executed:

#!/bin/bash# Get the full context of findMutableStaticIntField method
sed -n '100,160p' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 2937


🏁 Script executed:

#!/bin/bash# Check if there are name-based filters or constraints in the field resolution
rg -nP -B10 -A10 'findMutableStaticIntField' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -60

Repository: Tofaa2/EntityLib

Length of output: 2275


🏁 Script executed:

#!/bin/bash# Search for any additional validation or filtering of the resolved field
rg -nP 'entityLegacyField|resolveLegacySupplier' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -30

Repository: Tofaa2/EntityLib

Length of output: 620


Wildcard fallback selects the first non-final static int field, risking mutation of an unrelated field.

The method first attempts known field names ("entityCount", "b", "c") matching Minecraft entity counters, but if those fail, the wildcard fallback at lines 142–150 iterates getDeclaredFields() and returns the first non-final static int without name-based validation. Since getDeclaredFields() field ordering is not guaranteed across JVM implementations and obfuscation tools, this selection is non-deterministic. resolveLegacySupplier then calls setInt(null, entityId + 1) on this field (line 119), so selecting the wrong field mutates an unrelated NMS static int, corrupting server state.

While a fallback AtomicInteger exists if no field is found, the wildcard fallback should be restricted (e.g., require exactly one candidate, or log a warning) rather than blindly mutating any match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 142 - 150, The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.

@3add

3add commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Overall I think this class should be removed and spigot should just use SpigotReflectionUtil#813 and SpigotReflectionUtil#826 depending on whether the version is 26.2+

@PQguanfang

PQguanfang commented Jul 10, 2026

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotEntityIdProvider fix code, which can cause problems on the 26.1.2 server.

@Tofaa2

Copy link
Copy Markdown
Owner

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

@PQguanfang

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

Here is the problem:

if (serverVersion.isOlderThanOrEquals(ServerVersion.V_26_1)) {

In your code, the nextEntityId(World) method is used for servers with a version higher than 26.1; however, version 26.1.2 servers still use the nextEntityId() method. Therefore, you need to change the ServerVersion in this part of the code to 26_1_2.

https://github.com/PaperMC/Paper/blob/ver/26.1.2/paper-api/src/main/java/org/bukkit/UnsafeValues.java#L279

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TWME-TW@3add@PQguanfang@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix: Paper 26.2 compatibility for SpigotEntityIdProvider - #70

Open
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch
Open

fix: Paper 26.2 compatibility for SpigotEntityIdProvider#70
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch

Conversation

@TWME-TW

@TWME-TWTWME-TW commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Three fixes for the entity ID provider used on Paper 26.2+:

  1. Add reflection guard for UnsafeValues.nextEntityId()

    • Paper 26.2+ removed this method; verify via reflection before calling
    • Falls through to AtomicInteger reflection path when absent
  2. Fix ArrayIndexOutOfBoundsException in getEntityClass()

    • Paper 26.2+ has no version suffix in the CraftServer package name
    • Skip package name parsing entirely for 1.17+ (flattened) servers
  3. Fix IllegalAccessException on legacy entity counter

    • Skip static final fields (e.g. CURRENT_LEVEL)
    • Add findMutableStaticIntField helper with isFinal check
    • Fall back to local AtomicInteger when no mutable field is found
    • Also search for ENTITY_COUNTER in AtomicInteger field names

Summary by CodeRabbit

  • Bug Fixes
    • Improved compatibility with various Spigot/Paper/Minecraft server versions
    • Enhanced entity ID resolution to be more resilient across version variations
    • Added fallback mechanisms for improved stability when certain version-specific features are unavailable

Three fixes for the entity ID provider used on Paper 26.2+:
1. Add reflection guard for UnsafeValues.nextEntityId()
- Paper 26.2+ removed this method; verify via reflection before calling
- Falls through to AtomicInteger reflection path when absent
2. Fix ArrayIndexOutOfBoundsException in getEntityClass()
- Paper 26.2+ has no version suffix in the CraftServer package name
- Skip package name parsing entirely for 1.17+ (flattened) servers
3. Fix IllegalAccessException on legacy entity counter
- Skip static final fields (e.g. CURRENT_LEVEL)
- Add findMutableStaticIntField helper with isFinal check
- Fall back to local AtomicInteger when no mutable field is found
- Also search for ENTITY_COUNTER in AtomicInteger field names
@coderabbitai

coderabbitaiBot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

SpigotEntityIdProvider gains several defensive layers for entity-ID resolution: the Paper/1.16+ path now guards against missing nextEntityId via reflection, resolveAtomicSupplier adds "ENTITY_COUNTER" as a candidate name, resolveLegacySupplier falls back to a local AtomicInteger instead of throwing, a new findMutableStaticIntField helper performs flexible field scanning, and getEntityClass handles both flattened and pre-1.17 NMS package structures more defensively.

Changes

SpigotEntityIdProvider Resilience

Layer / File(s)Summary
Paper/1.16+ detection guard and atomic candidate extension
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
detectIdSupplier now checks for UnsafeValues.nextEntityId via reflection and falls through to the next strategy on NoSuchMethodException; resolveAtomicSupplier adds "ENTITY_COUNTER" to its candidate field name list.
Legacy supplier fallback and findMutableStaticIntField helper
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
resolveLegacySupplier replaces the hard throw with a local AtomicInteger fallback seeded near Integer.MAX_VALUE; the new findMutableStaticIntField method tries known field names then scans all non-final static int fields, returning null when none match.
Defensive NMS Entity class resolution
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
getEntityClass wraps ClassNotFoundException into IllegalStateException for the 1.17+ flattened path, and computes the pre-1.17 versioned package string with a safe fallback when server package parts are too short.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Tofaa2/EntityLib#45: Introduced the original detectIdSupplier, resolveAtomicSupplier, and resolveLegacySupplier logic that this PR directly extends and hardens.

Suggested reviewers

  • Tofaa2

Poem

🐇 Hop, hop through the NMS maze,
No more crashes on unknown field days!
ENTITY_COUNTER joins the hunt,
Fallback counters bear the brunt.
The bunny found each edge case true —
Defensive code, good as new! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title directly addresses the main change: fixing Paper 26.2 compatibility for SpigotEntityIdProvider, which is the primary focus of the changeset.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`:
- Around line 110-113: The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.
- Around line 142-150: The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92fe61a6-c265-47d5-b654-c3e8a5b4b550

📥 Commits

Reviewing files that changed from the base of the PR and between e797eee and 7a45be1.

📒 Files selected for processing (1)
  • platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Comment on lines +110 to +113
// Last resort: local high-offset counter. Entity ID collision is unlikely
// since the server allocates from 1 upward.
final AtomicInteger fallback = new AtomicInteger(Integer.MAX_VALUE - 100000);
return fallback::incrementAndGet;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fallback counter overflows into negative IDs after ~100k entities.

Seeding at Integer.MAX_VALUE - 100000 means incrementAndGet reaches Integer.MAX_VALUE after ~100k calls and then wraps to Integer.MIN_VALUE, producing negative entity IDs that can collide or misbehave on clients. Consider seeding lower (well above the server's allocation range but far from overflow), or documenting/bounding the wrap behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 110 - 113, The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.

Comment on lines +142 to +150
// Wildcard fallback: any non-final static int field
for (final Field field : clazz.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())
&& field.getType() == Integer.TYPE
&& !Modifier.isFinal(field.getModifiers())) {
return field;
}
}
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Inspect how the resolved legacy field is mutated
rg -nP -C3 '\bsetInt\s*\(' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 552


🏁 Script executed:

#!/bin/bash# Get the full context of findMutableStaticIntField method
sed -n '100,160p' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 2937


🏁 Script executed:

#!/bin/bash# Check if there are name-based filters or constraints in the field resolution
rg -nP -B10 -A10 'findMutableStaticIntField' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -60

Repository: Tofaa2/EntityLib

Length of output: 2275


🏁 Script executed:

#!/bin/bash# Search for any additional validation or filtering of the resolved field
rg -nP 'entityLegacyField|resolveLegacySupplier' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -30

Repository: Tofaa2/EntityLib

Length of output: 620


Wildcard fallback selects the first non-final static int field, risking mutation of an unrelated field.

The method first attempts known field names ("entityCount", "b", "c") matching Minecraft entity counters, but if those fail, the wildcard fallback at lines 142–150 iterates getDeclaredFields() and returns the first non-final static int without name-based validation. Since getDeclaredFields() field ordering is not guaranteed across JVM implementations and obfuscation tools, this selection is non-deterministic. resolveLegacySupplier then calls setInt(null, entityId + 1) on this field (line 119), so selecting the wrong field mutates an unrelated NMS static int, corrupting server state.

While a fallback AtomicInteger exists if no field is found, the wildcard fallback should be restricted (e.g., require exactly one candidate, or log a warning) rather than blindly mutating any match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 142 - 150, The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.

@3add

3add commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Overall I think this class should be removed and spigot should just use SpigotReflectionUtil#813 and SpigotReflectionUtil#826 depending on whether the version is 26.2+

@PQguanfang

PQguanfang commented Jul 10, 2026

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotEntityIdProvider fix code, which can cause problems on the 26.1.2 server.

@Tofaa2

Copy link
Copy Markdown
Owner

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

@PQguanfang

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

Here is the problem:

if (serverVersion.isOlderThanOrEquals(ServerVersion.V_26_1)) {

In your code, the nextEntityId(World) method is used for servers with a version higher than 26.1; however, version 26.1.2 servers still use the nextEntityId() method. Therefore, you need to change the ServerVersion in this part of the code to 26_1_2.

https://github.com/PaperMC/Paper/blob/ver/26.1.2/paper-api/src/main/java/org/bukkit/UnsafeValues.java#L279

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TWME-TW@3add@PQguanfang@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: Paper 26.2 compatibility for SpigotEntityIdProvider - #70

Open
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch
Open

fix: Paper 26.2 compatibility for SpigotEntityIdProvider#70
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch

Conversation

@TWME-TW

@TWME-TWTWME-TW commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Three fixes for the entity ID provider used on Paper 26.2+:

  1. Add reflection guard for UnsafeValues.nextEntityId()

    • Paper 26.2+ removed this method; verify via reflection before calling
    • Falls through to AtomicInteger reflection path when absent
  2. Fix ArrayIndexOutOfBoundsException in getEntityClass()

    • Paper 26.2+ has no version suffix in the CraftServer package name
    • Skip package name parsing entirely for 1.17+ (flattened) servers
  3. Fix IllegalAccessException on legacy entity counter

    • Skip static final fields (e.g. CURRENT_LEVEL)
    • Add findMutableStaticIntField helper with isFinal check
    • Fall back to local AtomicInteger when no mutable field is found
    • Also search for ENTITY_COUNTER in AtomicInteger field names

Summary by CodeRabbit

  • Bug Fixes
    • Improved compatibility with various Spigot/Paper/Minecraft server versions
    • Enhanced entity ID resolution to be more resilient across version variations
    • Added fallback mechanisms for improved stability when certain version-specific features are unavailable

Three fixes for the entity ID provider used on Paper 26.2+:
1. Add reflection guard for UnsafeValues.nextEntityId()
- Paper 26.2+ removed this method; verify via reflection before calling
- Falls through to AtomicInteger reflection path when absent
2. Fix ArrayIndexOutOfBoundsException in getEntityClass()
- Paper 26.2+ has no version suffix in the CraftServer package name
- Skip package name parsing entirely for 1.17+ (flattened) servers
3. Fix IllegalAccessException on legacy entity counter
- Skip static final fields (e.g. CURRENT_LEVEL)
- Add findMutableStaticIntField helper with isFinal check
- Fall back to local AtomicInteger when no mutable field is found
- Also search for ENTITY_COUNTER in AtomicInteger field names
@coderabbitai

coderabbitaiBot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

SpigotEntityIdProvider gains several defensive layers for entity-ID resolution: the Paper/1.16+ path now guards against missing nextEntityId via reflection, resolveAtomicSupplier adds "ENTITY_COUNTER" as a candidate name, resolveLegacySupplier falls back to a local AtomicInteger instead of throwing, a new findMutableStaticIntField helper performs flexible field scanning, and getEntityClass handles both flattened and pre-1.17 NMS package structures more defensively.

Changes

SpigotEntityIdProvider Resilience

Layer / File(s)Summary
Paper/1.16+ detection guard and atomic candidate extension
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
detectIdSupplier now checks for UnsafeValues.nextEntityId via reflection and falls through to the next strategy on NoSuchMethodException; resolveAtomicSupplier adds "ENTITY_COUNTER" to its candidate field name list.
Legacy supplier fallback and findMutableStaticIntField helper
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
resolveLegacySupplier replaces the hard throw with a local AtomicInteger fallback seeded near Integer.MAX_VALUE; the new findMutableStaticIntField method tries known field names then scans all non-final static int fields, returning null when none match.
Defensive NMS Entity class resolution
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
getEntityClass wraps ClassNotFoundException into IllegalStateException for the 1.17+ flattened path, and computes the pre-1.17 versioned package string with a safe fallback when server package parts are too short.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Tofaa2/EntityLib#45: Introduced the original detectIdSupplier, resolveAtomicSupplier, and resolveLegacySupplier logic that this PR directly extends and hardens.

Suggested reviewers

  • Tofaa2

Poem

🐇 Hop, hop through the NMS maze,
No more crashes on unknown field days!
ENTITY_COUNTER joins the hunt,
Fallback counters bear the brunt.
The bunny found each edge case true —
Defensive code, good as new! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title directly addresses the main change: fixing Paper 26.2 compatibility for SpigotEntityIdProvider, which is the primary focus of the changeset.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`:
- Around line 110-113: The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.
- Around line 142-150: The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92fe61a6-c265-47d5-b654-c3e8a5b4b550

📥 Commits

Reviewing files that changed from the base of the PR and between e797eee and 7a45be1.

📒 Files selected for processing (1)
  • platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Comment on lines +110 to +113
// Last resort: local high-offset counter. Entity ID collision is unlikely
// since the server allocates from 1 upward.
final AtomicInteger fallback = new AtomicInteger(Integer.MAX_VALUE - 100000);
return fallback::incrementAndGet;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fallback counter overflows into negative IDs after ~100k entities.

Seeding at Integer.MAX_VALUE - 100000 means incrementAndGet reaches Integer.MAX_VALUE after ~100k calls and then wraps to Integer.MIN_VALUE, producing negative entity IDs that can collide or misbehave on clients. Consider seeding lower (well above the server's allocation range but far from overflow), or documenting/bounding the wrap behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 110 - 113, The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.

Comment on lines +142 to +150
// Wildcard fallback: any non-final static int field
for (final Field field : clazz.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())
&& field.getType() == Integer.TYPE
&& !Modifier.isFinal(field.getModifiers())) {
return field;
}
}
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Inspect how the resolved legacy field is mutated
rg -nP -C3 '\bsetInt\s*\(' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 552


🏁 Script executed:

#!/bin/bash# Get the full context of findMutableStaticIntField method
sed -n '100,160p' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 2937


🏁 Script executed:

#!/bin/bash# Check if there are name-based filters or constraints in the field resolution
rg -nP -B10 -A10 'findMutableStaticIntField' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -60

Repository: Tofaa2/EntityLib

Length of output: 2275


🏁 Script executed:

#!/bin/bash# Search for any additional validation or filtering of the resolved field
rg -nP 'entityLegacyField|resolveLegacySupplier' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -30

Repository: Tofaa2/EntityLib

Length of output: 620


Wildcard fallback selects the first non-final static int field, risking mutation of an unrelated field.

The method first attempts known field names ("entityCount", "b", "c") matching Minecraft entity counters, but if those fail, the wildcard fallback at lines 142–150 iterates getDeclaredFields() and returns the first non-final static int without name-based validation. Since getDeclaredFields() field ordering is not guaranteed across JVM implementations and obfuscation tools, this selection is non-deterministic. resolveLegacySupplier then calls setInt(null, entityId + 1) on this field (line 119), so selecting the wrong field mutates an unrelated NMS static int, corrupting server state.

While a fallback AtomicInteger exists if no field is found, the wildcard fallback should be restricted (e.g., require exactly one candidate, or log a warning) rather than blindly mutating any match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 142 - 150, The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.

@3add

3add commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Overall I think this class should be removed and spigot should just use SpigotReflectionUtil#813 and SpigotReflectionUtil#826 depending on whether the version is 26.2+

@PQguanfang

PQguanfang commented Jul 10, 2026

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotEntityIdProvider fix code, which can cause problems on the 26.1.2 server.

@Tofaa2

Copy link
Copy Markdown
Owner

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

@PQguanfang

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

Here is the problem:

if (serverVersion.isOlderThanOrEquals(ServerVersion.V_26_1)) {

In your code, the nextEntityId(World) method is used for servers with a version higher than 26.1; however, version 26.1.2 servers still use the nextEntityId() method. Therefore, you need to change the ServerVersion in this part of the code to 26_1_2.

https://github.com/PaperMC/Paper/blob/ver/26.1.2/paper-api/src/main/java/org/bukkit/UnsafeValues.java#L279

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TWME-TW@3add@PQguanfang@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: Paper 26.2 compatibility for SpigotEntityIdProvider - #70

Open
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch
Open

fix: Paper 26.2 compatibility for SpigotEntityIdProvider#70
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch

Conversation

@TWME-TW

@TWME-TWTWME-TW commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Three fixes for the entity ID provider used on Paper 26.2+:

  1. Add reflection guard for UnsafeValues.nextEntityId()

    • Paper 26.2+ removed this method; verify via reflection before calling
    • Falls through to AtomicInteger reflection path when absent
  2. Fix ArrayIndexOutOfBoundsException in getEntityClass()

    • Paper 26.2+ has no version suffix in the CraftServer package name
    • Skip package name parsing entirely for 1.17+ (flattened) servers
  3. Fix IllegalAccessException on legacy entity counter

    • Skip static final fields (e.g. CURRENT_LEVEL)
    • Add findMutableStaticIntField helper with isFinal check
    • Fall back to local AtomicInteger when no mutable field is found
    • Also search for ENTITY_COUNTER in AtomicInteger field names

Summary by CodeRabbit

  • Bug Fixes
    • Improved compatibility with various Spigot/Paper/Minecraft server versions
    • Enhanced entity ID resolution to be more resilient across version variations
    • Added fallback mechanisms for improved stability when certain version-specific features are unavailable

Three fixes for the entity ID provider used on Paper 26.2+:
1. Add reflection guard for UnsafeValues.nextEntityId()
- Paper 26.2+ removed this method; verify via reflection before calling
- Falls through to AtomicInteger reflection path when absent
2. Fix ArrayIndexOutOfBoundsException in getEntityClass()
- Paper 26.2+ has no version suffix in the CraftServer package name
- Skip package name parsing entirely for 1.17+ (flattened) servers
3. Fix IllegalAccessException on legacy entity counter
- Skip static final fields (e.g. CURRENT_LEVEL)
- Add findMutableStaticIntField helper with isFinal check
- Fall back to local AtomicInteger when no mutable field is found
- Also search for ENTITY_COUNTER in AtomicInteger field names
@coderabbitai

coderabbitaiBot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

SpigotEntityIdProvider gains several defensive layers for entity-ID resolution: the Paper/1.16+ path now guards against missing nextEntityId via reflection, resolveAtomicSupplier adds "ENTITY_COUNTER" as a candidate name, resolveLegacySupplier falls back to a local AtomicInteger instead of throwing, a new findMutableStaticIntField helper performs flexible field scanning, and getEntityClass handles both flattened and pre-1.17 NMS package structures more defensively.

Changes

SpigotEntityIdProvider Resilience

Layer / File(s)Summary
Paper/1.16+ detection guard and atomic candidate extension
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
detectIdSupplier now checks for UnsafeValues.nextEntityId via reflection and falls through to the next strategy on NoSuchMethodException; resolveAtomicSupplier adds "ENTITY_COUNTER" to its candidate field name list.
Legacy supplier fallback and findMutableStaticIntField helper
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
resolveLegacySupplier replaces the hard throw with a local AtomicInteger fallback seeded near Integer.MAX_VALUE; the new findMutableStaticIntField method tries known field names then scans all non-final static int fields, returning null when none match.
Defensive NMS Entity class resolution
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
getEntityClass wraps ClassNotFoundException into IllegalStateException for the 1.17+ flattened path, and computes the pre-1.17 versioned package string with a safe fallback when server package parts are too short.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Tofaa2/EntityLib#45: Introduced the original detectIdSupplier, resolveAtomicSupplier, and resolveLegacySupplier logic that this PR directly extends and hardens.

Suggested reviewers

  • Tofaa2

Poem

🐇 Hop, hop through the NMS maze,
No more crashes on unknown field days!
ENTITY_COUNTER joins the hunt,
Fallback counters bear the brunt.
The bunny found each edge case true —
Defensive code, good as new! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title directly addresses the main change: fixing Paper 26.2 compatibility for SpigotEntityIdProvider, which is the primary focus of the changeset.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`:
- Around line 110-113: The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.
- Around line 142-150: The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92fe61a6-c265-47d5-b654-c3e8a5b4b550

📥 Commits

Reviewing files that changed from the base of the PR and between e797eee and 7a45be1.

📒 Files selected for processing (1)
  • platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Comment on lines +110 to +113
// Last resort: local high-offset counter. Entity ID collision is unlikely
// since the server allocates from 1 upward.
final AtomicInteger fallback = new AtomicInteger(Integer.MAX_VALUE - 100000);
return fallback::incrementAndGet;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fallback counter overflows into negative IDs after ~100k entities.

Seeding at Integer.MAX_VALUE - 100000 means incrementAndGet reaches Integer.MAX_VALUE after ~100k calls and then wraps to Integer.MIN_VALUE, producing negative entity IDs that can collide or misbehave on clients. Consider seeding lower (well above the server's allocation range but far from overflow), or documenting/bounding the wrap behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 110 - 113, The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.

Comment on lines +142 to +150
// Wildcard fallback: any non-final static int field
for (final Field field : clazz.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())
&& field.getType() == Integer.TYPE
&& !Modifier.isFinal(field.getModifiers())) {
return field;
}
}
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Inspect how the resolved legacy field is mutated
rg -nP -C3 '\bsetInt\s*\(' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 552


🏁 Script executed:

#!/bin/bash# Get the full context of findMutableStaticIntField method
sed -n '100,160p' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 2937


🏁 Script executed:

#!/bin/bash# Check if there are name-based filters or constraints in the field resolution
rg -nP -B10 -A10 'findMutableStaticIntField' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -60

Repository: Tofaa2/EntityLib

Length of output: 2275


🏁 Script executed:

#!/bin/bash# Search for any additional validation or filtering of the resolved field
rg -nP 'entityLegacyField|resolveLegacySupplier' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -30

Repository: Tofaa2/EntityLib

Length of output: 620


Wildcard fallback selects the first non-final static int field, risking mutation of an unrelated field.

The method first attempts known field names ("entityCount", "b", "c") matching Minecraft entity counters, but if those fail, the wildcard fallback at lines 142–150 iterates getDeclaredFields() and returns the first non-final static int without name-based validation. Since getDeclaredFields() field ordering is not guaranteed across JVM implementations and obfuscation tools, this selection is non-deterministic. resolveLegacySupplier then calls setInt(null, entityId + 1) on this field (line 119), so selecting the wrong field mutates an unrelated NMS static int, corrupting server state.

While a fallback AtomicInteger exists if no field is found, the wildcard fallback should be restricted (e.g., require exactly one candidate, or log a warning) rather than blindly mutating any match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 142 - 150, The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.

@3add

3add commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Overall I think this class should be removed and spigot should just use SpigotReflectionUtil#813 and SpigotReflectionUtil#826 depending on whether the version is 26.2+

@PQguanfang

PQguanfang commented Jul 10, 2026

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotEntityIdProvider fix code, which can cause problems on the 26.1.2 server.

@Tofaa2

Copy link
Copy Markdown
Owner

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

@PQguanfang

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

Here is the problem:

if (serverVersion.isOlderThanOrEquals(ServerVersion.V_26_1)) {

In your code, the nextEntityId(World) method is used for servers with a version higher than 26.1; however, version 26.1.2 servers still use the nextEntityId() method. Therefore, you need to change the ServerVersion in this part of the code to 26_1_2.

https://github.com/PaperMC/Paper/blob/ver/26.1.2/paper-api/src/main/java/org/bukkit/UnsafeValues.java#L279

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TWME-TW@3add@PQguanfang@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix: Paper 26.2 compatibility for SpigotEntityIdProvider - #70

Open
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch
Open

fix: Paper 26.2 compatibility for SpigotEntityIdProvider#70
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch

Conversation

@TWME-TW

@TWME-TWTWME-TW commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Three fixes for the entity ID provider used on Paper 26.2+:

  1. Add reflection guard for UnsafeValues.nextEntityId()

    • Paper 26.2+ removed this method; verify via reflection before calling
    • Falls through to AtomicInteger reflection path when absent
  2. Fix ArrayIndexOutOfBoundsException in getEntityClass()

    • Paper 26.2+ has no version suffix in the CraftServer package name
    • Skip package name parsing entirely for 1.17+ (flattened) servers
  3. Fix IllegalAccessException on legacy entity counter

    • Skip static final fields (e.g. CURRENT_LEVEL)
    • Add findMutableStaticIntField helper with isFinal check
    • Fall back to local AtomicInteger when no mutable field is found
    • Also search for ENTITY_COUNTER in AtomicInteger field names

Summary by CodeRabbit

  • Bug Fixes
    • Improved compatibility with various Spigot/Paper/Minecraft server versions
    • Enhanced entity ID resolution to be more resilient across version variations
    • Added fallback mechanisms for improved stability when certain version-specific features are unavailable

Three fixes for the entity ID provider used on Paper 26.2+:
1. Add reflection guard for UnsafeValues.nextEntityId()
- Paper 26.2+ removed this method; verify via reflection before calling
- Falls through to AtomicInteger reflection path when absent
2. Fix ArrayIndexOutOfBoundsException in getEntityClass()
- Paper 26.2+ has no version suffix in the CraftServer package name
- Skip package name parsing entirely for 1.17+ (flattened) servers
3. Fix IllegalAccessException on legacy entity counter
- Skip static final fields (e.g. CURRENT_LEVEL)
- Add findMutableStaticIntField helper with isFinal check
- Fall back to local AtomicInteger when no mutable field is found
- Also search for ENTITY_COUNTER in AtomicInteger field names
@coderabbitai

coderabbitaiBot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

SpigotEntityIdProvider gains several defensive layers for entity-ID resolution: the Paper/1.16+ path now guards against missing nextEntityId via reflection, resolveAtomicSupplier adds "ENTITY_COUNTER" as a candidate name, resolveLegacySupplier falls back to a local AtomicInteger instead of throwing, a new findMutableStaticIntField helper performs flexible field scanning, and getEntityClass handles both flattened and pre-1.17 NMS package structures more defensively.

Changes

SpigotEntityIdProvider Resilience

Layer / File(s)Summary
Paper/1.16+ detection guard and atomic candidate extension
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
detectIdSupplier now checks for UnsafeValues.nextEntityId via reflection and falls through to the next strategy on NoSuchMethodException; resolveAtomicSupplier adds "ENTITY_COUNTER" to its candidate field name list.
Legacy supplier fallback and findMutableStaticIntField helper
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
resolveLegacySupplier replaces the hard throw with a local AtomicInteger fallback seeded near Integer.MAX_VALUE; the new findMutableStaticIntField method tries known field names then scans all non-final static int fields, returning null when none match.
Defensive NMS Entity class resolution
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
getEntityClass wraps ClassNotFoundException into IllegalStateException for the 1.17+ flattened path, and computes the pre-1.17 versioned package string with a safe fallback when server package parts are too short.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Tofaa2/EntityLib#45: Introduced the original detectIdSupplier, resolveAtomicSupplier, and resolveLegacySupplier logic that this PR directly extends and hardens.

Suggested reviewers

  • Tofaa2

Poem

🐇 Hop, hop through the NMS maze,
No more crashes on unknown field days!
ENTITY_COUNTER joins the hunt,
Fallback counters bear the brunt.
The bunny found each edge case true —
Defensive code, good as new! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title directly addresses the main change: fixing Paper 26.2 compatibility for SpigotEntityIdProvider, which is the primary focus of the changeset.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`:
- Around line 110-113: The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.
- Around line 142-150: The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92fe61a6-c265-47d5-b654-c3e8a5b4b550

📥 Commits

Reviewing files that changed from the base of the PR and between e797eee and 7a45be1.

📒 Files selected for processing (1)
  • platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Comment on lines +110 to +113
// Last resort: local high-offset counter. Entity ID collision is unlikely
// since the server allocates from 1 upward.
final AtomicInteger fallback = new AtomicInteger(Integer.MAX_VALUE - 100000);
return fallback::incrementAndGet;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fallback counter overflows into negative IDs after ~100k entities.

Seeding at Integer.MAX_VALUE - 100000 means incrementAndGet reaches Integer.MAX_VALUE after ~100k calls and then wraps to Integer.MIN_VALUE, producing negative entity IDs that can collide or misbehave on clients. Consider seeding lower (well above the server's allocation range but far from overflow), or documenting/bounding the wrap behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 110 - 113, The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.

Comment on lines +142 to +150
// Wildcard fallback: any non-final static int field
for (final Field field : clazz.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())
&& field.getType() == Integer.TYPE
&& !Modifier.isFinal(field.getModifiers())) {
return field;
}
}
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Inspect how the resolved legacy field is mutated
rg -nP -C3 '\bsetInt\s*\(' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 552


🏁 Script executed:

#!/bin/bash# Get the full context of findMutableStaticIntField method
sed -n '100,160p' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 2937


🏁 Script executed:

#!/bin/bash# Check if there are name-based filters or constraints in the field resolution
rg -nP -B10 -A10 'findMutableStaticIntField' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -60

Repository: Tofaa2/EntityLib

Length of output: 2275


🏁 Script executed:

#!/bin/bash# Search for any additional validation or filtering of the resolved field
rg -nP 'entityLegacyField|resolveLegacySupplier' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -30

Repository: Tofaa2/EntityLib

Length of output: 620


Wildcard fallback selects the first non-final static int field, risking mutation of an unrelated field.

The method first attempts known field names ("entityCount", "b", "c") matching Minecraft entity counters, but if those fail, the wildcard fallback at lines 142–150 iterates getDeclaredFields() and returns the first non-final static int without name-based validation. Since getDeclaredFields() field ordering is not guaranteed across JVM implementations and obfuscation tools, this selection is non-deterministic. resolveLegacySupplier then calls setInt(null, entityId + 1) on this field (line 119), so selecting the wrong field mutates an unrelated NMS static int, corrupting server state.

While a fallback AtomicInteger exists if no field is found, the wildcard fallback should be restricted (e.g., require exactly one candidate, or log a warning) rather than blindly mutating any match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 142 - 150, The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.

@3add

3add commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Overall I think this class should be removed and spigot should just use SpigotReflectionUtil#813 and SpigotReflectionUtil#826 depending on whether the version is 26.2+

@PQguanfang

PQguanfang commented Jul 10, 2026

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotEntityIdProvider fix code, which can cause problems on the 26.1.2 server.

@Tofaa2

Copy link
Copy Markdown
Owner

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

@PQguanfang

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

Here is the problem:

if (serverVersion.isOlderThanOrEquals(ServerVersion.V_26_1)) {

In your code, the nextEntityId(World) method is used for servers with a version higher than 26.1; however, version 26.1.2 servers still use the nextEntityId() method. Therefore, you need to change the ServerVersion in this part of the code to 26_1_2.

https://github.com/PaperMC/Paper/blob/ver/26.1.2/paper-api/src/main/java/org/bukkit/UnsafeValues.java#L279

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TWME-TW@3add@PQguanfang@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: Paper 26.2 compatibility for SpigotEntityIdProvider - #70

Open
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch
Open

fix: Paper 26.2 compatibility for SpigotEntityIdProvider#70
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch

Conversation

@TWME-TW

@TWME-TWTWME-TW commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Three fixes for the entity ID provider used on Paper 26.2+:

  1. Add reflection guard for UnsafeValues.nextEntityId()

    • Paper 26.2+ removed this method; verify via reflection before calling
    • Falls through to AtomicInteger reflection path when absent
  2. Fix ArrayIndexOutOfBoundsException in getEntityClass()

    • Paper 26.2+ has no version suffix in the CraftServer package name
    • Skip package name parsing entirely for 1.17+ (flattened) servers
  3. Fix IllegalAccessException on legacy entity counter

    • Skip static final fields (e.g. CURRENT_LEVEL)
    • Add findMutableStaticIntField helper with isFinal check
    • Fall back to local AtomicInteger when no mutable field is found
    • Also search for ENTITY_COUNTER in AtomicInteger field names

Summary by CodeRabbit

  • Bug Fixes
    • Improved compatibility with various Spigot/Paper/Minecraft server versions
    • Enhanced entity ID resolution to be more resilient across version variations
    • Added fallback mechanisms for improved stability when certain version-specific features are unavailable

Three fixes for the entity ID provider used on Paper 26.2+:
1. Add reflection guard for UnsafeValues.nextEntityId()
- Paper 26.2+ removed this method; verify via reflection before calling
- Falls through to AtomicInteger reflection path when absent
2. Fix ArrayIndexOutOfBoundsException in getEntityClass()
- Paper 26.2+ has no version suffix in the CraftServer package name
- Skip package name parsing entirely for 1.17+ (flattened) servers
3. Fix IllegalAccessException on legacy entity counter
- Skip static final fields (e.g. CURRENT_LEVEL)
- Add findMutableStaticIntField helper with isFinal check
- Fall back to local AtomicInteger when no mutable field is found
- Also search for ENTITY_COUNTER in AtomicInteger field names
@coderabbitai

coderabbitaiBot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

SpigotEntityIdProvider gains several defensive layers for entity-ID resolution: the Paper/1.16+ path now guards against missing nextEntityId via reflection, resolveAtomicSupplier adds "ENTITY_COUNTER" as a candidate name, resolveLegacySupplier falls back to a local AtomicInteger instead of throwing, a new findMutableStaticIntField helper performs flexible field scanning, and getEntityClass handles both flattened and pre-1.17 NMS package structures more defensively.

Changes

SpigotEntityIdProvider Resilience

Layer / File(s)Summary
Paper/1.16+ detection guard and atomic candidate extension
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
detectIdSupplier now checks for UnsafeValues.nextEntityId via reflection and falls through to the next strategy on NoSuchMethodException; resolveAtomicSupplier adds "ENTITY_COUNTER" to its candidate field name list.
Legacy supplier fallback and findMutableStaticIntField helper
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
resolveLegacySupplier replaces the hard throw with a local AtomicInteger fallback seeded near Integer.MAX_VALUE; the new findMutableStaticIntField method tries known field names then scans all non-final static int fields, returning null when none match.
Defensive NMS Entity class resolution
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
getEntityClass wraps ClassNotFoundException into IllegalStateException for the 1.17+ flattened path, and computes the pre-1.17 versioned package string with a safe fallback when server package parts are too short.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Tofaa2/EntityLib#45: Introduced the original detectIdSupplier, resolveAtomicSupplier, and resolveLegacySupplier logic that this PR directly extends and hardens.

Suggested reviewers

  • Tofaa2

Poem

🐇 Hop, hop through the NMS maze,
No more crashes on unknown field days!
ENTITY_COUNTER joins the hunt,
Fallback counters bear the brunt.
The bunny found each edge case true —
Defensive code, good as new! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title directly addresses the main change: fixing Paper 26.2 compatibility for SpigotEntityIdProvider, which is the primary focus of the changeset.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`:
- Around line 110-113: The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.
- Around line 142-150: The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92fe61a6-c265-47d5-b654-c3e8a5b4b550

📥 Commits

Reviewing files that changed from the base of the PR and between e797eee and 7a45be1.

📒 Files selected for processing (1)
  • platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Comment on lines +110 to +113
// Last resort: local high-offset counter. Entity ID collision is unlikely
// since the server allocates from 1 upward.
final AtomicInteger fallback = new AtomicInteger(Integer.MAX_VALUE - 100000);
return fallback::incrementAndGet;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fallback counter overflows into negative IDs after ~100k entities.

Seeding at Integer.MAX_VALUE - 100000 means incrementAndGet reaches Integer.MAX_VALUE after ~100k calls and then wraps to Integer.MIN_VALUE, producing negative entity IDs that can collide or misbehave on clients. Consider seeding lower (well above the server's allocation range but far from overflow), or documenting/bounding the wrap behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 110 - 113, The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.

Comment on lines +142 to +150
// Wildcard fallback: any non-final static int field
for (final Field field : clazz.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())
&& field.getType() == Integer.TYPE
&& !Modifier.isFinal(field.getModifiers())) {
return field;
}
}
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Inspect how the resolved legacy field is mutated
rg -nP -C3 '\bsetInt\s*\(' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 552


🏁 Script executed:

#!/bin/bash# Get the full context of findMutableStaticIntField method
sed -n '100,160p' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 2937


🏁 Script executed:

#!/bin/bash# Check if there are name-based filters or constraints in the field resolution
rg -nP -B10 -A10 'findMutableStaticIntField' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -60

Repository: Tofaa2/EntityLib

Length of output: 2275


🏁 Script executed:

#!/bin/bash# Search for any additional validation or filtering of the resolved field
rg -nP 'entityLegacyField|resolveLegacySupplier' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -30

Repository: Tofaa2/EntityLib

Length of output: 620


Wildcard fallback selects the first non-final static int field, risking mutation of an unrelated field.

The method first attempts known field names ("entityCount", "b", "c") matching Minecraft entity counters, but if those fail, the wildcard fallback at lines 142–150 iterates getDeclaredFields() and returns the first non-final static int without name-based validation. Since getDeclaredFields() field ordering is not guaranteed across JVM implementations and obfuscation tools, this selection is non-deterministic. resolveLegacySupplier then calls setInt(null, entityId + 1) on this field (line 119), so selecting the wrong field mutates an unrelated NMS static int, corrupting server state.

While a fallback AtomicInteger exists if no field is found, the wildcard fallback should be restricted (e.g., require exactly one candidate, or log a warning) rather than blindly mutating any match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 142 - 150, The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.

@3add

3add commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Overall I think this class should be removed and spigot should just use SpigotReflectionUtil#813 and SpigotReflectionUtil#826 depending on whether the version is 26.2+

@PQguanfang

PQguanfang commented Jul 10, 2026

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotEntityIdProvider fix code, which can cause problems on the 26.1.2 server.

@Tofaa2

Copy link
Copy Markdown
Owner

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

@PQguanfang

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

Here is the problem:

if (serverVersion.isOlderThanOrEquals(ServerVersion.V_26_1)) {

In your code, the nextEntityId(World) method is used for servers with a version higher than 26.1; however, version 26.1.2 servers still use the nextEntityId() method. Therefore, you need to change the ServerVersion in this part of the code to 26_1_2.

https://github.com/PaperMC/Paper/blob/ver/26.1.2/paper-api/src/main/java/org/bukkit/UnsafeValues.java#L279

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TWME-TW@3add@PQguanfang@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: Paper 26.2 compatibility for SpigotEntityIdProvider - #70

Open
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch
Open

fix: Paper 26.2 compatibility for SpigotEntityIdProvider#70
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch

Conversation

@TWME-TW

@TWME-TWTWME-TW commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Three fixes for the entity ID provider used on Paper 26.2+:

  1. Add reflection guard for UnsafeValues.nextEntityId()

    • Paper 26.2+ removed this method; verify via reflection before calling
    • Falls through to AtomicInteger reflection path when absent
  2. Fix ArrayIndexOutOfBoundsException in getEntityClass()

    • Paper 26.2+ has no version suffix in the CraftServer package name
    • Skip package name parsing entirely for 1.17+ (flattened) servers
  3. Fix IllegalAccessException on legacy entity counter

    • Skip static final fields (e.g. CURRENT_LEVEL)
    • Add findMutableStaticIntField helper with isFinal check
    • Fall back to local AtomicInteger when no mutable field is found
    • Also search for ENTITY_COUNTER in AtomicInteger field names

Summary by CodeRabbit

  • Bug Fixes
    • Improved compatibility with various Spigot/Paper/Minecraft server versions
    • Enhanced entity ID resolution to be more resilient across version variations
    • Added fallback mechanisms for improved stability when certain version-specific features are unavailable

Three fixes for the entity ID provider used on Paper 26.2+:
1. Add reflection guard for UnsafeValues.nextEntityId()
- Paper 26.2+ removed this method; verify via reflection before calling
- Falls through to AtomicInteger reflection path when absent
2. Fix ArrayIndexOutOfBoundsException in getEntityClass()
- Paper 26.2+ has no version suffix in the CraftServer package name
- Skip package name parsing entirely for 1.17+ (flattened) servers
3. Fix IllegalAccessException on legacy entity counter
- Skip static final fields (e.g. CURRENT_LEVEL)
- Add findMutableStaticIntField helper with isFinal check
- Fall back to local AtomicInteger when no mutable field is found
- Also search for ENTITY_COUNTER in AtomicInteger field names
@coderabbitai

coderabbitaiBot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

SpigotEntityIdProvider gains several defensive layers for entity-ID resolution: the Paper/1.16+ path now guards against missing nextEntityId via reflection, resolveAtomicSupplier adds "ENTITY_COUNTER" as a candidate name, resolveLegacySupplier falls back to a local AtomicInteger instead of throwing, a new findMutableStaticIntField helper performs flexible field scanning, and getEntityClass handles both flattened and pre-1.17 NMS package structures more defensively.

Changes

SpigotEntityIdProvider Resilience

Layer / File(s)Summary
Paper/1.16+ detection guard and atomic candidate extension
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
detectIdSupplier now checks for UnsafeValues.nextEntityId via reflection and falls through to the next strategy on NoSuchMethodException; resolveAtomicSupplier adds "ENTITY_COUNTER" to its candidate field name list.
Legacy supplier fallback and findMutableStaticIntField helper
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
resolveLegacySupplier replaces the hard throw with a local AtomicInteger fallback seeded near Integer.MAX_VALUE; the new findMutableStaticIntField method tries known field names then scans all non-final static int fields, returning null when none match.
Defensive NMS Entity class resolution
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
getEntityClass wraps ClassNotFoundException into IllegalStateException for the 1.17+ flattened path, and computes the pre-1.17 versioned package string with a safe fallback when server package parts are too short.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Tofaa2/EntityLib#45: Introduced the original detectIdSupplier, resolveAtomicSupplier, and resolveLegacySupplier logic that this PR directly extends and hardens.

Suggested reviewers

  • Tofaa2

Poem

🐇 Hop, hop through the NMS maze,
No more crashes on unknown field days!
ENTITY_COUNTER joins the hunt,
Fallback counters bear the brunt.
The bunny found each edge case true —
Defensive code, good as new! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title directly addresses the main change: fixing Paper 26.2 compatibility for SpigotEntityIdProvider, which is the primary focus of the changeset.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`:
- Around line 110-113: The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.
- Around line 142-150: The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92fe61a6-c265-47d5-b654-c3e8a5b4b550

📥 Commits

Reviewing files that changed from the base of the PR and between e797eee and 7a45be1.

📒 Files selected for processing (1)
  • platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Comment on lines +110 to +113
// Last resort: local high-offset counter. Entity ID collision is unlikely
// since the server allocates from 1 upward.
final AtomicInteger fallback = new AtomicInteger(Integer.MAX_VALUE - 100000);
return fallback::incrementAndGet;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fallback counter overflows into negative IDs after ~100k entities.

Seeding at Integer.MAX_VALUE - 100000 means incrementAndGet reaches Integer.MAX_VALUE after ~100k calls and then wraps to Integer.MIN_VALUE, producing negative entity IDs that can collide or misbehave on clients. Consider seeding lower (well above the server's allocation range but far from overflow), or documenting/bounding the wrap behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 110 - 113, The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.

Comment on lines +142 to +150
// Wildcard fallback: any non-final static int field
for (final Field field : clazz.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())
&& field.getType() == Integer.TYPE
&& !Modifier.isFinal(field.getModifiers())) {
return field;
}
}
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Inspect how the resolved legacy field is mutated
rg -nP -C3 '\bsetInt\s*\(' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 552


🏁 Script executed:

#!/bin/bash# Get the full context of findMutableStaticIntField method
sed -n '100,160p' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 2937


🏁 Script executed:

#!/bin/bash# Check if there are name-based filters or constraints in the field resolution
rg -nP -B10 -A10 'findMutableStaticIntField' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -60

Repository: Tofaa2/EntityLib

Length of output: 2275


🏁 Script executed:

#!/bin/bash# Search for any additional validation or filtering of the resolved field
rg -nP 'entityLegacyField|resolveLegacySupplier' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -30

Repository: Tofaa2/EntityLib

Length of output: 620


Wildcard fallback selects the first non-final static int field, risking mutation of an unrelated field.

The method first attempts known field names ("entityCount", "b", "c") matching Minecraft entity counters, but if those fail, the wildcard fallback at lines 142–150 iterates getDeclaredFields() and returns the first non-final static int without name-based validation. Since getDeclaredFields() field ordering is not guaranteed across JVM implementations and obfuscation tools, this selection is non-deterministic. resolveLegacySupplier then calls setInt(null, entityId + 1) on this field (line 119), so selecting the wrong field mutates an unrelated NMS static int, corrupting server state.

While a fallback AtomicInteger exists if no field is found, the wildcard fallback should be restricted (e.g., require exactly one candidate, or log a warning) rather than blindly mutating any match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 142 - 150, The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.

@3add

3add commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Overall I think this class should be removed and spigot should just use SpigotReflectionUtil#813 and SpigotReflectionUtil#826 depending on whether the version is 26.2+

@PQguanfang

PQguanfang commented Jul 10, 2026

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotEntityIdProvider fix code, which can cause problems on the 26.1.2 server.

@Tofaa2

Copy link
Copy Markdown
Owner

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

@PQguanfang

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

Here is the problem:

if (serverVersion.isOlderThanOrEquals(ServerVersion.V_26_1)) {

In your code, the nextEntityId(World) method is used for servers with a version higher than 26.1; however, version 26.1.2 servers still use the nextEntityId() method. Therefore, you need to change the ServerVersion in this part of the code to 26_1_2.

https://github.com/PaperMC/Paper/blob/ver/26.1.2/paper-api/src/main/java/org/bukkit/UnsafeValues.java#L279

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TWME-TW@3add@PQguanfang@Tofaa2
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix: Paper 26.2 compatibility for SpigotEntityIdProvider - #70

Open
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch
Open

fix: Paper 26.2 compatibility for SpigotEntityIdProvider#70
TWME-TW wants to merge 1 commit into
Tofaa2:masterfrom
TWME-TW:26.2-patch

Conversation

@TWME-TW

@TWME-TWTWME-TW commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Three fixes for the entity ID provider used on Paper 26.2+:

  1. Add reflection guard for UnsafeValues.nextEntityId()

    • Paper 26.2+ removed this method; verify via reflection before calling
    • Falls through to AtomicInteger reflection path when absent
  2. Fix ArrayIndexOutOfBoundsException in getEntityClass()

    • Paper 26.2+ has no version suffix in the CraftServer package name
    • Skip package name parsing entirely for 1.17+ (flattened) servers
  3. Fix IllegalAccessException on legacy entity counter

    • Skip static final fields (e.g. CURRENT_LEVEL)
    • Add findMutableStaticIntField helper with isFinal check
    • Fall back to local AtomicInteger when no mutable field is found
    • Also search for ENTITY_COUNTER in AtomicInteger field names

Summary by CodeRabbit

  • Bug Fixes
    • Improved compatibility with various Spigot/Paper/Minecraft server versions
    • Enhanced entity ID resolution to be more resilient across version variations
    • Added fallback mechanisms for improved stability when certain version-specific features are unavailable

Three fixes for the entity ID provider used on Paper 26.2+:
1. Add reflection guard for UnsafeValues.nextEntityId()
- Paper 26.2+ removed this method; verify via reflection before calling
- Falls through to AtomicInteger reflection path when absent
2. Fix ArrayIndexOutOfBoundsException in getEntityClass()
- Paper 26.2+ has no version suffix in the CraftServer package name
- Skip package name parsing entirely for 1.17+ (flattened) servers
3. Fix IllegalAccessException on legacy entity counter
- Skip static final fields (e.g. CURRENT_LEVEL)
- Add findMutableStaticIntField helper with isFinal check
- Fall back to local AtomicInteger when no mutable field is found
- Also search for ENTITY_COUNTER in AtomicInteger field names
@coderabbitai

coderabbitaiBot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

SpigotEntityIdProvider gains several defensive layers for entity-ID resolution: the Paper/1.16+ path now guards against missing nextEntityId via reflection, resolveAtomicSupplier adds "ENTITY_COUNTER" as a candidate name, resolveLegacySupplier falls back to a local AtomicInteger instead of throwing, a new findMutableStaticIntField helper performs flexible field scanning, and getEntityClass handles both flattened and pre-1.17 NMS package structures more defensively.

Changes

SpigotEntityIdProvider Resilience

Layer / File(s)Summary
Paper/1.16+ detection guard and atomic candidate extension
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
detectIdSupplier now checks for UnsafeValues.nextEntityId via reflection and falls through to the next strategy on NoSuchMethodException; resolveAtomicSupplier adds "ENTITY_COUNTER" to its candidate field name list.
Legacy supplier fallback and findMutableStaticIntField helper
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
resolveLegacySupplier replaces the hard throw with a local AtomicInteger fallback seeded near Integer.MAX_VALUE; the new findMutableStaticIntField method tries known field names then scans all non-final static int fields, returning null when none match.
Defensive NMS Entity class resolution
platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java
getEntityClass wraps ClassNotFoundException into IllegalStateException for the 1.17+ flattened path, and computes the pre-1.17 versioned package string with a safe fallback when server package parts are too short.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Tofaa2/EntityLib#45: Introduced the original detectIdSupplier, resolveAtomicSupplier, and resolveLegacySupplier logic that this PR directly extends and hardens.

Suggested reviewers

  • Tofaa2

Poem

🐇 Hop, hop through the NMS maze,
No more crashes on unknown field days!
ENTITY_COUNTER joins the hunt,
Fallback counters bear the brunt.
The bunny found each edge case true —
Defensive code, good as new! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title directly addresses the main change: fixing Paper 26.2 compatibility for SpigotEntityIdProvider, which is the primary focus of the changeset.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`:
- Around line 110-113: The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.
- Around line 142-150: The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92fe61a6-c265-47d5-b654-c3e8a5b4b550

📥 Commits

Reviewing files that changed from the base of the PR and between e797eee and 7a45be1.

📒 Files selected for processing (1)
  • platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Comment on lines +110 to +113
// Last resort: local high-offset counter. Entity ID collision is unlikely
// since the server allocates from 1 upward.
final AtomicInteger fallback = new AtomicInteger(Integer.MAX_VALUE - 100000);
return fallback::incrementAndGet;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fallback counter overflows into negative IDs after ~100k entities.

Seeding at Integer.MAX_VALUE - 100000 means incrementAndGet reaches Integer.MAX_VALUE after ~100k calls and then wraps to Integer.MIN_VALUE, producing negative entity IDs that can collide or misbehave on clients. Consider seeding lower (well above the server's allocation range but far from overflow), or documenting/bounding the wrap behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 110 - 113, The fallback counter in the `SpigotEntityIdProvider`
class is seeded at `Integer.MAX_VALUE - 100000`, causing it to overflow into
negative values after approximately 100k entity creations. Replace the seed
value in the `AtomicInteger` initialization to use a lower value that maintains
sufficient distance from the server's normal allocation range (which starts at
1) while leaving adequate headroom before `Integer.MAX_VALUE` to prevent
overflow, such as a value in the range of 1,000,000 or higher depending on
expected entity count limits, or alternatively implement bounds checking or
wrapping logic to handle the overflow gracefully.

Comment on lines +142 to +150
// Wildcard fallback: any non-final static int field
for (final Field field : clazz.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())
&& field.getType() == Integer.TYPE
&& !Modifier.isFinal(field.getModifiers())) {
return field;
}
}
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Inspect how the resolved legacy field is mutated
rg -nP -C3 '\bsetInt\s*\(' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 552


🏁 Script executed:

#!/bin/bash# Get the full context of findMutableStaticIntField method
sed -n '100,160p' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java

Repository: Tofaa2/EntityLib

Length of output: 2937


🏁 Script executed:

#!/bin/bash# Check if there are name-based filters or constraints in the field resolution
rg -nP -B10 -A10 'findMutableStaticIntField' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -60

Repository: Tofaa2/EntityLib

Length of output: 2275


🏁 Script executed:

#!/bin/bash# Search for any additional validation or filtering of the resolved field
rg -nP 'entityLegacyField|resolveLegacySupplier' platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java | head -30

Repository: Tofaa2/EntityLib

Length of output: 620


Wildcard fallback selects the first non-final static int field, risking mutation of an unrelated field.

The method first attempts known field names ("entityCount", "b", "c") matching Minecraft entity counters, but if those fail, the wildcard fallback at lines 142–150 iterates getDeclaredFields() and returns the first non-final static int without name-based validation. Since getDeclaredFields() field ordering is not guaranteed across JVM implementations and obfuscation tools, this selection is non-deterministic. resolveLegacySupplier then calls setInt(null, entityId + 1) on this field (line 119), so selecting the wrong field mutates an unrelated NMS static int, corrupting server state.

While a fallback AtomicInteger exists if no field is found, the wildcard fallback should be restricted (e.g., require exactly one candidate, or log a warning) rather than blindly mutating any match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityIdProvider.java`
around lines 142 - 150, The wildcard fallback logic in the field iteration for
non-final static int fields (lines 142-150) blindly returns the first matching
field without validation, risking mutation of unrelated NMS static integers when
resolveLegacySupplier calls setInt on this field. Instead of returning the first
match found by getDeclaredFields(), add validation to ensure you are selecting
the correct field: either collect all matching candidates and verify exactly one
exists before returning it, or add a warning log if the fallback is being used,
or implement additional checks to distinguish legitimate entity counter fields
from unrelated static ints. This prevents silent corruption of server state from
mutating the wrong field.

@3add

3add commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Overall I think this class should be removed and spigot should just use SpigotReflectionUtil#813 and SpigotReflectionUtil#826 depending on whether the version is 26.2+

@PQguanfang

PQguanfang commented Jul 10, 2026

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotEntityIdProvider fix code, which can cause problems on the 26.1.2 server.

@Tofaa2

Copy link
Copy Markdown
Owner

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

@PQguanfang

Copy link
Copy Markdown

@Tofaa2 There seems to be an issue with your SpigotElementIdProvider fix code, which can cause problems on the 26.1.2 server.

Could you elaborate further

Here is the problem:

if (serverVersion.isOlderThanOrEquals(ServerVersion.V_26_1)) {

In your code, the nextEntityId(World) method is used for servers with a version higher than 26.1; however, version 26.1.2 servers still use the nextEntityId() method. Therefore, you need to change the ServerVersion in this part of the code to 26_1_2.

https://github.com/PaperMC/Paper/blob/ver/26.1.2/paper-api/src/main/java/org/bukkit/UnsafeValues.java#L279

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TWME-TW@3add@PQguanfang@Tofaa2