LoParkour/release/GUIDE.md
2026-03-17 13:36:23 +01:00

230 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# LoParkour — Гайд по патчам
Все файлы в этом архиве заменяют оригинальные файлы проекта **1 к 1 по пути**.
Просто скопируй их поверх своего src/ и пересобери проект.
---
## Что уже исправлено (этот архив)
### 🔴 Патч 1 — SQL Injection (КРИТИЧНО)
**Файлы:** `storage/StorageSQL.java`, `storage/SQLQueryExecutor.java`, `storage/SQLMigrationManager.java`
Оригинальный код вставлял имена игроков, стили, локали напрямую в SQL строки через `.formatted()`.
Игрок с ником `'; DROP TABLE loparkour-leaderboard-default; --` мог уничтожить таблицу лидерборда.
**Что сделано:** Все запросы с пользовательскими данными переписаны на `?` плейсхолдеры.
JDBC-драйвер сам экранирует значения — инъекция физически невозможна.
---
### 🔴 Патч 2 — ElytraMode полная переработка
**Файлы:** `mode/ElytraMode.java`, `mode/ElytraGenerator.java`, `resources/config.yml`, `resources/locales/en.yml`
Оригинальный ElytraMode был просто обычным паркуром с элитрой в инвентаре.
Кольца лежали горизонтально (как блины), хитбокс был кубом 5×5×5,
cooldown фейерверка не работал (показывал сообщение но не блокировал).
**Что сделано:**
- Генерируются кольца в воздухе по плавной кривой (настраивается в config.yml)
- Кольца стоят вертикально, перпендикулярно направлению полёта
- Детект пролёта через плоскость кольца (dot product, а не куб)
- Firework перехватывается через `PlayerInteractEvent` — реально блокируется до использования
- Boost выдаётся вручную через `setVelocity()` + визуальный фейерверк без урона
- Action bar: счёт + дистанция до следующего кольца + cooldown
---
### 🟠 Патч 3 — SpeedrunMode не работал вообще
**Файл:** `mode/SpeedrunMode.java`
`onBlockTouch()` был объявлен но **нигде не вызывался** — блоки никогда не исчезали.
Speedrun был идентичен обычному режиму.
**Что сделано:** Подключился через `score()` override — вызывается в момент когда
игрок встаёт на новый блок. Исправлено деление на 50 которое давало 0 тиков при
малых значениях. Warning task теперь отменяется при reset().
---
### 🟠 Патч 4 — CoopMode показывался в одиночном меню
**Файл:** `mode/CoopMode.java`
`CoopMode implements Mode` вместо `MultiMode` — поэтому висел в меню рядом с Default/Speedrun.
`SingleMenu` фильтрует `instanceof MultiMode`, но кооп им не был.
**Что сделано:** `CoopMode implements MultiMode`. `getItem()` возвращает `null`.
`create()` открывает лобби вместо создания сессии соло.
Исправлен двойной счётчик (`sharedScore` дублировал `state.score`).
`fall()` больше не чистит стейт который уже сброшен внутри `super.fall()`.
---
### 🟠 Патч 5 — RaceMode off-by-one + ActionBar конфликт
**Файл:** `mode/RaceMode.java`
Проверка финиша была в `tick()` — срабатывала на тик позже нужного.
ActionBar обновлялся каждый тик конфликтуя с parent.
`reset(false)` вызывался с задержкой без null-check — NPE если игрок вышел.
**Что сделано:** Финиш проверяется в `score()` override — точно в момент достижения цели.
Прогресс-бар обновляется только при новом очке. Добавлен guard `!state.stopped`.
---
## Что ещё нужно исправить (следующие шаги)
### 🔴 Приоритет 1 — Ghost файлы по имени игрока (path traversal + коллизии)
**Файл:** `ghost/GhostManager.java`
```java
// ПРОБЛЕМА:
File file = new File(modeFolder, data.getPlayerName() + ".ghost");
```
Два игрока с одинаковым именем затирают друг друга.
Игрок с именем `../../config` может писать за пределы папки плагина.
**Как чинить:** Называть файлы по UUID игрока, не по имени.
Хранить имя внутри файла (оно уже там есть в `GhostData`).
---
### 🟡 Приоритет 2 — Ghost не пишет всех игроков в мультиплеере
**Файл:** `generator/lifecycle/GeneratorLifecycle.java`
`GhostRecorder` записывает только `generator.player` (первый игрок сессии).
В CoopMode и RaceMode остальные игроки не записываются.
**Как чинить:** Recorder на каждого игрока отдельно, или писать только в single-player режимах.
---
### 🟡 Приоритет 3 — `disable()` глотает все исключения
**Файл:** `LoParkour.java`
```java
} catch (Throwable ignored) { }
```
Если при выключении сервера не сохранятся данные — никто не узнает.
**Как чинить:** Минимум логировать: `catch (Throwable t) { getLogger().severe(...) }`.
---
### 🟡 Приоритет 4 — `cleanupDistantBlocks()` вызывается дважды за тик
**Файл:** `generator/lifecycle/GeneratorLifecycle.java`
Вызывается явно в `tick()` И через отдельный `cleanupTask` таймер одновременно.
**Как чинить:** Убрать явный вызов из `tick()`, оставить только таймер.
---
### 🟡 Приоритет 5 — `new Random()` в горячих методах
**Файл:** `generator/jump/BlockPlacer.java`
```java
pool.get(new java.util.Random().nextInt(pool.size())) // внутри tryGenerateSchematic()
new java.util.Random().nextInt(rotations.length) // внутри rotatedPaste()
```
Каждый вызов создаёт новый объект Random. Дорого и неправильно.
**Как чинить:** Один `private static final ThreadLocalRandom RNG = ThreadLocalRandom.current()`
на уровне класса.
---
### 🟡 Приоритет 6 — `System.err.println` в JumpOffsetGenerator
**Файл:** `generator/jump/JumpOffsetGenerator.java`
```java
System.err.println("Warning: Unexpected jump height ...");
```
В Bukkit плагинах нельзя писать в System.err напрямую.
**Как чинить:** `LoParkour.getPlugin().getLogger().warning(...)`.
---
### 🟢 Приоритет 7 — Пустой javadoc в LoParkour.java
**Файл:** `LoParkour.java`
Висит осиротевший javadoc от удалённого метода `getLogging()`. Не баг, просто мусор.
---
## Порядок следующих патчей (рекомендация)
1. Ghost path traversal (безопасность)
2. `disable()` silent catch (надёжность)
3. `cleanupDistantBlocks` двойной вызов (производительность)
4. `new Random()` в горячих путях (производительность)
5. Ghost мультиплеер (функциональность)
---
## Патчи добавленные в эту версию архива
### 🔴 Патч 6 — Ghost path traversal + коллизии по имени (БЕЗОПАСНОСТЬ)
**Файлы:** `ghost/GhostData.java`, `ghost/GhostManager.java`, `ghost/GhostRecorder.java`, `generator/lifecycle/GeneratorLifecycle.java`
Файлы назывались `playerName.ghost`. Два игрока с одинаковым ником затирали друг друга.
Игрок с именем `../../config` мог писать файлы за пределы папки плагина (path traversal).
**Что сделано:**
- Файлы теперь называются `uuid.ghost` — уникально и filesystem-safe
- `GhostData` хранит UUID (добавлен в конструктор и в бинарный формат)
- `GhostRecorder.stopRecording()` принимает UUID + имя
- `GhostManager.sanitizeMode()` чистит имя режима от слэшей
- Старые файлы v1 (без UUID) автоматически удаляются при загрузке с логом
- Формат файла версионирован (int VERSION=2 в начале)
---
### 🟡 Патч 7 — `cleanupDistantBlocks()` вызывался дважды за тик
**Файл:** `generator/lifecycle/GeneratorLifecycle.java`
Был явный вызов в `tick()` И таймер `cleanupTask` — двойная работа каждый тик.
**Что сделано:** Убран явный вызов из `tick()`. Только таймер.
---
### 🟡 Патч 8 — `new Random()` в горячих методах BlockPlacer
**Файл:** `generator/jump/BlockPlacer.java`
`new java.util.Random()` создавался внутри `tryGenerateSchematic()` и `rotatedPaste()` при каждом вызове.
**Что сделано:** Заменено на `ThreadLocalRandom.current().nextInt()`.
---
### 🟡 Патч 9 — `System.err.println` в JumpOffsetGenerator
**Файл:** `generator/jump/JumpOffsetGenerator.java`
**Что сделано:** Заменено на `LoParkour.getPlugin().getLogger().warning(...)`.
---
### 🟡 Патч 10 — `disable()` глотал все исключения молча
**Файл:** `LoParkour.java`
`catch (Throwable ignored) {}` — если данные не сохранялись при выключении сервера, никто не узнавал.
**Что сделано:** `catch (Throwable t)` с логированием и `t.printStackTrace()`.
---
## Оставшиеся задачи
### 🟡 Ghost мультиплеер
`GhostRecorder` пишет только `generator.player` (первый игрок сессии).
В CoopMode/RaceMode остальные игроки не записываются.
**Решение:** Либо отдельный рекордер на каждого, либо запись только в single-player режимах.
### 🟢 Осиротевший javadoc в LoParkour.java
Комментарий от удалённого метода `getLogging()` — просто мусор, не баг.