# 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()` — просто мусор, не баг.