feat: LAN 최대 인원 8 → 30 (단일 jar, 확장 불가 버전은 8 유지)
Some checks failed
build / build (push) Has been cancelled
Some checks failed
build / build (push) Has been cancelled
바닐라 "LAN 에 공개" 의 상수 8 을 믹스인 1개로 30 으로 바꾼다. 대상과 메서드를 문자열로만 적고 핸들러를 int -> int 로 만들어 마인크래프트 타입 참조 0 을 유지한다. 8 이 있는 자리 (바닐라 jar 바이트코드 실측, 버전마다 정확히 한 곳): - 1.17 ~ 1.21.8 class_1130.<init> (super 인자 → PlayerList.maxPlayers) - 1.21.9 ~ 1.21.11 class_1132.method_3802 (return 8) - 26.1 ~ 26.3 IntegratedServer.getMaxPlayers (공식 이름) @Pseudo + require=0 이라 자리를 못 찾는 버전은 크래시 없이 바닐라 8 로 돈다. 적용 여부는 웹 콘솔 "최대 인원" 과 로그로 보인다. tools/lan-limit-probe: 바닐라 jar + Fabric Loader 로 모드를 실제 로드해 확인하는 도구. 1.18.2 ~ 26.3 (11개 버전, 로더 0.19.5 / 0.14.25) 전부 30, 믹스인을 뺀 jar 는 8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -84,7 +84,7 @@ GUI 코드가 한 줄도 필요 없다.
|
||||
|
||||
- 버전별 빌드 17개 → 1개
|
||||
- Stonecutter 조건부 주석 (기존 구현은 GUI 한 파일에만 96개) → 0개
|
||||
- 믹스인 14개 → 0개. 다른 모드와 충돌할 지점도 함께 사라졌다.
|
||||
- 믹스인 14개 → 1개(LAN 최대 인원 전용, 7절). 다른 모드와 충돌할 지점도 거의 사라졌다.
|
||||
- Fabric Loom·매핑·마인크래프트 jar 다운로드 없음 → 빌드가 몇 초로 끝나고 CI 매트릭스도 1칸
|
||||
- 게임 API 를 안 건드리므로 새 마인크래프트 버전이 나와도 할 일이 없다
|
||||
|
||||
@@ -219,12 +219,61 @@ JDK 안에서 끝내면 그 변수가 사라진다.
|
||||
|
||||
---
|
||||
|
||||
## 7. 남은 과제
|
||||
## 7. 최대 인원: 믹스인 1개를 들인 이유
|
||||
|
||||
"LAN 에 공개" 는 8명이 한계다. 클라이언트 코드에 상수로 박혀 있어 네트워크 쪽에서 우회할 수
|
||||
없다 — 참가 판정(`PlayerList.canPlayerLogin`)이 `players.size() >= 최대값` 을 직접 본다.
|
||||
30명 방을 만들려면 게임 코드의 그 8 을 바꾸는 수밖에 없고, Fabric 에서 그 수단은 믹스인뿐이다.
|
||||
|
||||
2절의 원칙을 깨지 않는 방법이 있었다. **대상과 메서드를 문자열로만 적고, 핸들러를 `int -> int`
|
||||
로 만들면 믹스인 클래스에 마인크래프트 타입이 하나도 들어가지 않는다.** 그러면 이름 체계가 다른
|
||||
여러 버전의 대상을 한 클래스에 나란히 적을 수 있다.
|
||||
|
||||
### 8 이 있는 자리 (실측)
|
||||
|
||||
바닐라 클라이언트 jar 를 버전별로 내려받아 `javap` 로 `bipush 8` 을 셌다. 두 클래스
|
||||
(`IntegratedServer`, `IntegratedPlayerList`)를 합쳐 **모든 버전에서 정확히 한 곳**이었다.
|
||||
|
||||
| 버전 | 이름 체계 | 위치 |
|
||||
|---|---|---|
|
||||
| 1.17.1 ~ 1.21.8 | intermediary | `class_1130.<init>` — `super(..., 8)` 인자, `PlayerList.maxPlayers` 필드로 간다 |
|
||||
| 1.21.9 ~ 1.21.11 | intermediary | `class_1132.method_3802` — `return 8` |
|
||||
| 26.1 ~ 26.3 | 공식 이름 | `IntegratedServer.getMaxPlayers` — `return 8` |
|
||||
|
||||
1.21.9 에서 `PlayerList.maxPlayers` 필드가 사라지고 판정이 `server.getMaxPlayers()` 로 바뀌었다.
|
||||
즉 "필드를 리플렉션으로 바꾸기" 는 1.21.8 까지만 통한다. 최신 버전까지 덮으려면 메서드 본문의
|
||||
상수를 바꿔야 해서 리플렉션이 아니라 믹스인이어야 했다.
|
||||
|
||||
### 설계
|
||||
|
||||
`LanMaxPlayersMixin` 하나가 세 대상을 동시에 노린다.
|
||||
|
||||
- `@Pseudo` — 실행 중인 버전에 없는 대상 클래스는 건너뛴다.
|
||||
- `@ModifyConstant(intValue = 8, require = 0)` — 메서드 선택자 `<init>`, `method_3802`,
|
||||
`getMaxPlayers` 중 있는 것에서 8 을 찾는다. 없으면 실패하지 않는다.
|
||||
- 설정 파일도 `required: false`, `defaultRequire: 0`.
|
||||
- 1.21.8 이하에서 8 은 `super()` 호출 전이라 `this` 가 없다. 그래서 핸들러가 static 이다.
|
||||
- 핸들러는 `MaxPlayers.replace` 를 부르고, 8 이 아닌 값은 그대로 돌려준다.
|
||||
|
||||
결과적으로 미래 버전에서 자리가 바뀌면 이 믹스인은 아무것도 안 하고 게임은 8명으로 돈다.
|
||||
"올릴 수 있는 버전은 30, 아니면 8" 이 jar 하나에서 저절로 갈린다. 실제 적용 여부는
|
||||
`MaxPlayers.raised()` 로 웹 콘솔에 표시한다.
|
||||
|
||||
치른 비용: 시작 로그에 "없는 대상 클래스" WARN 이 한두 줄 찍힌다(`@Pseudo` 의 정상 동작).
|
||||
그리고 새 마인크래프트 버전이 나오면 이 표 한 줄은 다시 확인해야 한다. 2절에서 말한 "새 버전에
|
||||
할 일이 없다" 는 이 기능에 한해서는 "확인할 일이 하나 있다" 로 바뀐다. 확인은
|
||||
`tools/lan-limit-probe` 로 한다 — 바닐라 jar 와 Fabric Loader 를 받아 모드를 실제로 로드하고,
|
||||
게임 메인 직전에 대상 클래스를 불러 값이 30 인지 본다.
|
||||
|
||||
---
|
||||
|
||||
## 8. 남은 과제
|
||||
|
||||
우선순위 순.
|
||||
|
||||
1. **실제 게임 검증.** 루프백 테스트는 프로토콜 계층까지만 본다. 1.21.x 와 26.x 양쪽에서
|
||||
실제로 로드되고 접속되는지 확인해야 한다. 나머지 항목보다 이게 먼저다.
|
||||
1. **실제 게임 검증.** 루프백 테스트는 프로토콜 계층까지만 본다. Fabric Loader 위에서 모드가
|
||||
로드되고 최대 인원 믹스인이 적용되는 것까지는 1.18.2 ~ 26.3 에서 확인했다(7절). 화면을 띄운
|
||||
실제 게임에서 접속되는지, 9명 이상이 실제로 들어오는지는 아직이다. 나머지 항목보다 이게 먼저다.
|
||||
2. **실제 NAT 뒤에서의 홀펀칭 성공률 측정.** 같은 기기 안 테스트에는 NAT 가 없다. 대칭 NAT·
|
||||
CGNAT 비율에 따라 중계 의존도가 결정되므로 숫자를 봐야 다음 판단을 할 수 있다.
|
||||
3. **시그널링 TLS.** 지금은 평문 TCP 라 이 경로를 장악한 중간자가 인증서 지문을 바꿔칠 수
|
||||
|
||||
Reference in New Issue
Block a user