{"record":{"id":"06f4d7270c5c041a","repo":"HMCL-dev/HMCL","slug":"missing-project-id-or-file-id","errorCode":null,"errorMessage":"Missing Project ID or File ID.","messagePattern":"Missing Project ID or File ID\\.","errorType":"validation","errorClass":"JsonParseException","httpStatus":null,"severity":"error","filePath":"HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/curse/CurseManifestFile.java","lineNumber":39,"sourceCode":"import com.google.gson.annotations.SerializedName;\nimport org.jackhuang.hmcl.util.gson.JsonSerializable;\nimport org.jackhuang.hmcl.util.gson.Validation;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Objects;\n\n/// @author huangyuhui\n@JsonSerializable\npublic record CurseManifestFile(@SerializedName(\"projectID\") int projectID,\n                                @SerializedName(\"fileID\") int fileID,\n                                @SerializedName(\"fileName\") String fileName,\n                                @SerializedName(\"url\") String url,\n                                @SerializedName(\"required\") boolean required) implements Validation {\n\n    @Override\n    public void validate() throws JsonParseException {\n        if (projectID == 0 || fileID == 0)\n            throw new JsonParseException(\"Missing Project ID or File ID.\");\n    }\n\n    @Override\n    @Nullable\n    public String url() {\n        if (url == null) {\n            return fileName != null\n                    ? String.format(\"https://edge.forgecdn.net/files/%d/%d/%s\", fileID / 1000, fileID % 1000, fileName)\n                    : null;\n        } else {\n            return url;\n        }\n    }\n\n    public CurseManifestFile withFileName(String fileName) {\n        return new CurseManifestFile(projectID, fileID, fileName, url, required);\n    }\n","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/curse/CurseManifestFile.java#L21-L57","documentation":"CurseManifestFile.validate() throws JsonParseException when either projectID or fileID is 0, since valid CurseForge file entries must reference both a project and a file. These IDs are used to build download URLs via the CurseForge API.","triggerScenarios":"Parsing a curse manifest whose files[] entry contains projectID=0 and/or fileID=0 (missing or defaulted numeric fields), then calling validate().","commonSituations":"Hand-crafted or incorrectly exported manifest.json, fields accidentally removed or renamed (JSON key mismatch such as projectId vs projectID), placeholders left unfilled.","solutions":["Fill in the correct CurseForge projectID and fileID for each manifest files[] entry","Re-export the modpack from the CurseForge app to regenerate valid IDs","Check JSON key spelling matches the @SerializedName annotations (projectID, fileID)"],"exampleFix":"// before\n{\"files\":[{\"projectID\":0,\"fileID\":0,\"required\":true}]}\n// after\n{\"files\":[{\"projectID\":238222,\"fileID\":2919425,\"required\":true}]}","handlingStrategy":"validation","validationCode":"manifest.getFiles().forEach(f -> { if (f.getProjectID() == 0 || f.getFileID() == 0) throw new IllegalArgumentException(\"Bad CurseForge file entry: \" + f); });","typeGuard":"boolean hasCurseIds(CurseManifestFile f) { return f.getProjectID() != 0 && f.getFileID() != 0; }","tryCatchPattern":"try { manifestFile.validate(); } catch (JsonParseException e) { log.error(\"Manifest file entry missing IDs\", e); }","preventionTips":["Keep manifest.json key names exactly as the CurseForge spec (projectID/fileID)","Re-export manifests with the official CurseForge exporter","Validate manifests before distributing packs"],"tags":["curseforge","modpack","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}