{"record":{"id":"0679e605238a836c","repo":"Anuken/Mindustry","slug":"too-many-plans","errorCode":null,"errorMessage":"Too many plans: ","messagePattern":"Too many plans: ","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"core/src/mindustry/io/TypeIO.java","lineNumber":702,"sourceCode":"        write.s((short)plans.size);\n        //each plan should be ~7-12 bytes\n        for(BuildPlan plan : plans){\n            if(plan.breaking) throw new RuntimeException(\"Breaking plans should not be sent.\");\n            write.i(Point2.pack(plan.x, plan.y));\n\n            write.s(plan.block.id);\n            if(plan.block.rotate){ //no need to write rotation for blocks that don't use it\n                write.b((byte)plan.rotation);\n            }\n            writeObject(write, validClientPlanConfig(plan.config) ? plan.config : null);\n        }\n    }\n\n    public static ClientBuildPlans readClientPlans(Reads read){\n        short amount = read.s();\n\n        if(amount == 0) return null;\n        if(amount > maxPlayerPreviewPlans) throw new RuntimeException(\"Too many plans: \" + amount);\n\n        var result = new ClientBuildPlans();\n        result.ensureCapacity(Math.min(amount, maxPlayerPreviewPlans));\n        for(int i = 0; i < amount; i++){\n            int x = read.us();\n            int y = read.us();\n            Block block = Vars.content.block(read.us());\n            if(block == null) throw new ArcRuntimeException(\"Invalid block ID in block plans! Client is likely using an incorrectly configured mod.\");\n            int rotation = (block.rotate ? read.b() : 0);\n            Object config = readClientPlanConfig(read);\n            BuildPlan plan = new BuildPlan(x, y, rotation, block, config);\n            result.add(plan);\n        }\n\n        return result;\n    }\n\n    static @Nullable Object readClientPlanConfig(Reads read){","sourceCodeStart":684,"sourceCodeEnd":720,"githubUrl":"https://github.com/Anuken/Mindustry/blob/f695ad7e60323ebced984fa26d0bcf0bc54296b4/core/src/mindustry/io/TypeIO.java#L684-L720","documentation":"readClientPlans() reads a client-to-server preview-plan packet. It reads a short amount and throws if amount > maxPlayerPreviewPlans (1000). Clients normally truncate to this limit (NetClient.java:784, NetServer.java:1381), so exceeding it means the sender bypassed truncation or is hostile.","triggerScenarios":"A client preview-plans packet's short count field exceeds 1000.","commonSituations":"A modified client that skips truncation; a mod; packet fuzzing.","solutions":["Ensure client-side truncation to maxPlayerPreviewPlans before send.","Server: catch the RuntimeException and kick the sender."],"exampleFix":"// before (client sends everything)\npreviewPlansAssembling.addAll(all);\n\n// after (client truncates to the cap)\nint added = Math.min(all.size, maxPlayerPreviewPlans - previewPlansAssembling.size);\nfor(int i = 0; i < added; i++) previewPlansAssembling.add(all.get(i));","handlingStrategy":"validation","validationCode":"// Before sending preview plans, truncate to the cap.\nif(plans.size > maxPlayerPreviewPlans){\n    throw new IllegalStateException(\"Too many preview plans: \" + plans.size);\n}","typeGuard":null,"tryCatchPattern":"try { ClientBuildPlans p = TypeIO.readClientPlans(read); }\ncatch(RuntimeException e){ Log.err(\"Too many client plans\", e); con.kick(\"Invalid packet.\"); }","preventionTips":["Always truncate preview plans to 1000 client-side before sending (NetClient.java:784).","Treat over-limit counts server-side as a modified client."],"tags":["network","build-plan","deserialization","security"],"backgroundTag":null,"analyzedSha":"f695ad7e60323ebced984fa26d0bcf0bc54296b4","analyzedAt":"2026-08-14T04:31:16.262Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}