From 4770cbe91ca5ff1cfe8b25bc1dad5f6bfa7905b6 Mon Sep 17 00:00:00 2001 From: Techcable Date: Sun, 15 Mar 2015 11:32:21 -0700 Subject: [PATCH 1/9] Add Command Line Interface for Patching --- src/main/java/net/md_5/jbeat/Main.java | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/main/java/net/md_5/jbeat/Main.java diff --git a/src/main/java/net/md_5/jbeat/Main.java b/src/main/java/net/md_5/jbeat/Main.java new file mode 100644 index 0000000..bc198e0 --- /dev/null +++ b/src/main/java/net/md_5/jbeat/Main.java @@ -0,0 +1,32 @@ +import java.io.File; + +public class Main { + public static void main(String[] args) { + if (args.length < 3) { + System.err.println("You must specify 3 arguments!"); + System.err.println("\tjava -jar jbeat.jar original.jar patch.bps output.jar") + System.err.println("In This will apply patch.bps to original.jar and safe to output.jar"); + return; + } + File in = new File(args[0]); + File patch = new File(args[1]); + File out = new File(args[2]); + + if (out.exists()) { + System.err.println("The output file " + args[2] + " exists.") + System.err.println("Please delete it before running this program"); + return; + } + out.createNewFile() + + try { + Patcher patcher = new Patcher(patch, in, out); + } catch (Exception e) { + System.err.println("Unable to patch file"); + e.printStackTrace(); + return; + } + + System.out.prinln("Successfuly Patched: " args[0] " with " + args[1]); + } +} From ed4eeaa25718f357ed37c048ddf4f5ca89d3d21d Mon Sep 17 00:00:00 2001 From: Techcable Date: Sun, 15 Mar 2015 11:39:24 -0700 Subject: [PATCH 2/9] Add JCommander and Main Class --- pom.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pom.xml b/pom.xml index 1c14d40..e90274e 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,14 @@ + + + com.beust + jcommander + 1.47 + + + md_5 @@ -48,6 +56,7 @@ + jbeat com.mycila.maven-license-plugin @@ -78,6 +87,18 @@ 1.5 + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + net.md_5.jbeat.Main + + + + From 8f7d291c7eb73cd499638302f2e18b4eb1d871e5 Mon Sep 17 00:00:00 2001 From: Techcable Date: Sun, 15 Mar 2015 19:40:01 +0000 Subject: [PATCH 3/9] Add Ability to Create Patch --- .gitignore | 3 +- pom.xml | 21 +++- src/main/java/net/md_5/jbeat/Main.java | 135 ++++++++++++++++++++++--- 3 files changed, 141 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 306af6f..e721a1f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ /build.xml # maven +/dependency-reduced-pom.xml /target # vim @@ -29,4 +30,4 @@ *.iml *.ipr *.iws -.idea/ \ No newline at end of file +.idea/ diff --git a/pom.xml b/pom.xml index e90274e..bb0d609 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ net.md-5 jbeat - 1.1-SNAPSHOT + 1.2-SNAPSHOT jar jbeat @@ -30,6 +30,12 @@ jcommander 1.47 + + org.projectlombok + lombok + 1.14.8 + provided + @@ -99,6 +105,19 @@ + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + + diff --git a/src/main/java/net/md_5/jbeat/Main.java b/src/main/java/net/md_5/jbeat/Main.java index bc198e0..a4711f7 100644 --- a/src/main/java/net/md_5/jbeat/Main.java +++ b/src/main/java/net/md_5/jbeat/Main.java @@ -1,32 +1,135 @@ +/** + * Copyright (c) 2012, md_5. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package net.md_5.jbeat; + import java.io.File; +import java.io.FileNotFoundException; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; +import com.beust.jcommander.converters.FileConverter; + +import lombok.*; public class Main { - public static void main(String[] args) { - if (args.length < 3) { - System.err.println("You must specify 3 arguments!"); - System.err.println("\tjava -jar jbeat.jar original.jar patch.bps output.jar") - System.err.println("In This will apply patch.bps to original.jar and safe to output.jar"); - return; + public static void main(String[] rawArgs) { + JBeatOptions options = new JBeatOptions(); + new JCommander(options, rawArgs); + if (options.getFirstFile() != null && options.getSecondFile() != null) { + createPatch(options.getFirstFile(), options.getSecondFile(), options.getPatchOut(), true); + } else if (options.getInFile() != null && options.getOutFile() != null && options.getPatchIn() != null) { + patch(options.getInFile(), options.getPatchIn(), options.getOutFile()); + } else { + System.err.println("You must either specify --first-file and --second-file to generate a patch"); + System.err.println("Or --in-file --patch-in and --out-file to patch a file"); + System.exit(1); } - File in = new File(args[0]); - File patch = new File(args[1]); - File out = new File(args[2]); - + } + + public static void createPatch(File first, File second, File patch, boolean linear) { + if (patch.exists()) { + System.err.println("The patch file " + patch.getName() + " exists."); + System.err.println("Please delete it before running this program"); + System.exit(1); + } + if (!linear) throw new UnsupportedOperationException("Only linear patch creation is supported"); + createFile(patch); + PatchCreator patchCreator = null; + try { + patchCreator = linear ? new LinearCreator(first, second, patch) : null; + } catch (FileNotFoundException e) { + System.err.println("Unable to open file: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + try { + patchCreator.create(); + } catch (Exception e) { + System.err.println("Unable to create patch of " + first.getName() + " and " + second.getName()); + e.printStackTrace(); + System.exit(1); + } + System.out.println("Successfuly created patch of " + first.getName() + " and " + second.getName() + " in " + patch.getName()); + } + + public static void patch(File in, File patch, File out) { if (out.exists()) { - System.err.println("The output file " + args[2] + " exists.") + System.err.println("The output file " + out.getName() + " exists."); System.err.println("Please delete it before running this program"); - return; + System.exit(1); } - out.createNewFile() + createFile(out); try { Patcher patcher = new Patcher(patch, in, out); } catch (Exception e) { - System.err.println("Unable to patch file"); + System.err.println("Unable to patch file " + in.getName()); + e.printStackTrace(); + System.exit(1); + } + + System.out.println("Successfuly Patched: " + in.getName() + " with " + patch.getName()); + } + + private static void createFile(File f) { + try { + f.createNewFile(); + } catch (Exception e) { + System.err.println("Unable to create file:" + e.getMessage()); e.printStackTrace(); - return; + System.exit(1); } + } + + @Getter + public static class JBeatOptions { + @Parameter(names = {"-a", "--first-file"}, description = "Original file for generating patch", converter = FileConverter.class) + private File firstFile; + + @Parameter(names = {"-b", "--second-file"}, description = "Changed file for generating patch", converter = FileConverter.class) + private File secondFile; + + @Parameter(names = {"-o", "--patch-out"}, description = "The file to put the generated patch in", converter = FileConverter.class) + private File patchOut = new File("output.bps"); + + @Parameter(names = {"-i", "--in-file"}, description = "Input file to patch", converter = FileConverter.class) + private File inFile; + + @Parameter(names = {"-b", "--out-file"}, description = "Write patched file here", converter = FileConverter.class) + private File outFile; + + @Parameter(names = {"-p", "--patch-in"}, description = "Patch to use to patch in file", converter = FileConverter.class) + private File patchIn; - System.out.prinln("Successfuly Patched: " args[0] " with " + args[1]); + /* JBeat only supports linear patches + @Paramater(names = {"-l", "--linear-patch"}, description = "Create binary patches in a linear fashion without delta compression.") + private boolean linearPatch = true; + */ } } From 9bcf1961d942d3cab621f652d21b2e7fbe2ff4f5 Mon Sep 17 00:00:00 2001 From: Techcable Date: Sun, 15 Mar 2015 20:04:03 +0000 Subject: [PATCH 4/9] Actually patch the files Fix ambiguously named options --- src/main/java/net/md_5/jbeat/Main.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/md_5/jbeat/Main.java b/src/main/java/net/md_5/jbeat/Main.java index a4711f7..0ded6d2 100644 --- a/src/main/java/net/md_5/jbeat/Main.java +++ b/src/main/java/net/md_5/jbeat/Main.java @@ -88,6 +88,7 @@ public static void patch(File in, File patch, File out) { try { Patcher patcher = new Patcher(patch, in, out); + patcher.patch(); } catch (Exception e) { System.err.println("Unable to patch file " + in.getName()); e.printStackTrace(); @@ -115,13 +116,13 @@ public static class JBeatOptions { @Parameter(names = {"-b", "--second-file"}, description = "Changed file for generating patch", converter = FileConverter.class) private File secondFile; - @Parameter(names = {"-o", "--patch-out"}, description = "The file to put the generated patch in", converter = FileConverter.class) + @Parameter(names = {"--patch-out"}, description = "The file to put the generated patch in", converter = FileConverter.class) private File patchOut = new File("output.bps"); @Parameter(names = {"-i", "--in-file"}, description = "Input file to patch", converter = FileConverter.class) private File inFile; - @Parameter(names = {"-b", "--out-file"}, description = "Write patched file here", converter = FileConverter.class) + @Parameter(names = {"-o", "--out-file"}, description = "Write patched file here", converter = FileConverter.class) private File outFile; @Parameter(names = {"-p", "--patch-in"}, description = "Patch to use to patch in file", converter = FileConverter.class) From 67c3c21d4872cb525bf9f5d4d2150c8e7d22287b Mon Sep 17 00:00:00 2001 From: Techcable Date: Sun, 15 Mar 2015 20:07:29 +0000 Subject: [PATCH 5/9] Finalize version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bb0d609..f449428 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ net.md-5 jbeat - 1.2-SNAPSHOT + 1.2 jar jbeat From 1c03a3daed41ee7954f98cf4d5964457d0d70db2 Mon Sep 17 00:00:00 2001 From: Techcable Date: Sun, 5 Jul 2015 19:25:47 -0700 Subject: [PATCH 6/9] Switch to techcable parent --- pom.xml | 21 +++++----- .../java/net/md_5/jbeat/LinearCreator.java | 40 ++++++++----------- src/main/java/net/md_5/jbeat/Main.java | 40 ++++++++----------- .../java/net/md_5/jbeat/PatchCreator.java | 40 ++++++++----------- src/main/java/net/md_5/jbeat/Patcher.java | 40 ++++++++----------- src/main/java/net/md_5/jbeat/Shared.java | 40 ++++++++----------- 6 files changed, 96 insertions(+), 125 deletions(-) mode change 100644 => 100755 pom.xml diff --git a/pom.xml b/pom.xml old mode 100644 new mode 100755 index f449428..9d58190 --- a/pom.xml +++ b/pom.xml @@ -2,14 +2,14 @@ 4.0.0 - org.sonatype.oss - oss-parent - 7 + net.techcable + parent + 1.0.5 net.md-5 jbeat - 1.2 + 1.3-SNAPSHOT jar jbeat @@ -30,12 +30,6 @@ jcommander 1.47 - - org.projectlombok - lombok - 1.14.8 - provided - @@ -61,6 +55,13 @@ UTF-8 + + + techcable-repo + http://repo.techcable.net/content/groups/public + + + jbeat diff --git a/src/main/java/net/md_5/jbeat/LinearCreator.java b/src/main/java/net/md_5/jbeat/LinearCreator.java index e275e2b..1985fda 100644 --- a/src/main/java/net/md_5/jbeat/LinearCreator.java +++ b/src/main/java/net/md_5/jbeat/LinearCreator.java @@ -1,30 +1,24 @@ /** - * Copyright (c) 2012, md_5. All rights reserved. + * The MIT License + * Copyright (c) 2015 Techcable * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * The name of the author may not be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package net.md_5.jbeat; diff --git a/src/main/java/net/md_5/jbeat/Main.java b/src/main/java/net/md_5/jbeat/Main.java index 0ded6d2..c1c6a16 100644 --- a/src/main/java/net/md_5/jbeat/Main.java +++ b/src/main/java/net/md_5/jbeat/Main.java @@ -1,30 +1,24 @@ /** - * Copyright (c) 2012, md_5. All rights reserved. + * The MIT License + * Copyright (c) 2015 Techcable * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * The name of the author may not be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package net.md_5.jbeat; diff --git a/src/main/java/net/md_5/jbeat/PatchCreator.java b/src/main/java/net/md_5/jbeat/PatchCreator.java index 3f57a76..0c089c7 100644 --- a/src/main/java/net/md_5/jbeat/PatchCreator.java +++ b/src/main/java/net/md_5/jbeat/PatchCreator.java @@ -1,30 +1,24 @@ /** - * Copyright (c) 2012, md_5. All rights reserved. + * The MIT License + * Copyright (c) 2015 Techcable * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * The name of the author may not be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package net.md_5.jbeat; diff --git a/src/main/java/net/md_5/jbeat/Patcher.java b/src/main/java/net/md_5/jbeat/Patcher.java index a78ebdf..d0fcd10 100644 --- a/src/main/java/net/md_5/jbeat/Patcher.java +++ b/src/main/java/net/md_5/jbeat/Patcher.java @@ -1,30 +1,24 @@ /** - * Copyright (c) 2012, md_5. All rights reserved. + * The MIT License + * Copyright (c) 2015 Techcable * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * The name of the author may not be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package net.md_5.jbeat; diff --git a/src/main/java/net/md_5/jbeat/Shared.java b/src/main/java/net/md_5/jbeat/Shared.java index 3889b90..16a4e52 100644 --- a/src/main/java/net/md_5/jbeat/Shared.java +++ b/src/main/java/net/md_5/jbeat/Shared.java @@ -1,30 +1,24 @@ /** - * Copyright (c) 2012, md_5. All rights reserved. + * The MIT License + * Copyright (c) 2015 Techcable * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * The name of the author may not be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package net.md_5.jbeat; From b50e3a2e422ccc9de9c19039afaa9c1dbc8cd337 Mon Sep 17 00:00:00 2001 From: Techcable Date: Sun, 5 Jul 2015 19:51:46 -0700 Subject: [PATCH 7/9] Switch from RandomAccessFile input -> ByteBuffer input Allows to generate patches from in memory or remote --- .../java/net/md_5/jbeat/LinearCreator.java | 12 +- src/main/java/net/md_5/jbeat/Main.java | 24 +++- .../java/net/md_5/jbeat/PatchCreator.java | 130 +++++++----------- 3 files changed, 82 insertions(+), 84 deletions(-) mode change 100644 => 100755 src/main/java/net/md_5/jbeat/LinearCreator.java mode change 100644 => 100755 src/main/java/net/md_5/jbeat/Main.java mode change 100644 => 100755 src/main/java/net/md_5/jbeat/PatchCreator.java diff --git a/src/main/java/net/md_5/jbeat/LinearCreator.java b/src/main/java/net/md_5/jbeat/LinearCreator.java old mode 100644 new mode 100755 index 1985fda..fc29717 --- a/src/main/java/net/md_5/jbeat/LinearCreator.java +++ b/src/main/java/net/md_5/jbeat/LinearCreator.java @@ -25,6 +25,8 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.ByteBuffer; + import static net.md_5.jbeat.Shared.*; /** @@ -35,12 +37,12 @@ public final class LinearCreator extends PatchCreator { private int targetReadLength, targetRelativeOffset, outputOffset; - public LinearCreator(File original, File modified, File output) throws FileNotFoundException { - super(original, modified, output); + public LinearCreator(ByteBuffer source, long sourceLength, ByteBuffer modified, long modifiedLength, ByteBuffer output, String header) { + super(source, sourceLength, modified, modifiedLength, output, header); } - public LinearCreator(File original, File modified, File output, String header) throws FileNotFoundException { - super(original, modified, output, header); + public LinearCreator(ByteBuffer source, long sourceLength, ByteBuffer modified, long modifiedLength, ByteBuffer output) { + super(source, sourceLength, modified, modifiedLength, output, null); } @Override @@ -94,7 +96,7 @@ private void targetReadFlush() throws IOException { encode(out, TARGET_READ | ((targetReadLength - 1) << 2)); int offset = outputOffset - targetReadLength; while (targetReadLength != 0) { - out.write(target.get(offset++)); + out.put(target.get(offset++)); targetReadLength--; } } diff --git a/src/main/java/net/md_5/jbeat/Main.java b/src/main/java/net/md_5/jbeat/Main.java old mode 100644 new mode 100755 index c1c6a16..6a746c6 --- a/src/main/java/net/md_5/jbeat/Main.java +++ b/src/main/java/net/md_5/jbeat/Main.java @@ -24,6 +24,10 @@ import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel.MapMode; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; @@ -55,19 +59,37 @@ public static void createPatch(File first, File second, File patch, boolean line if (!linear) throw new UnsupportedOperationException("Only linear patch creation is supported"); createFile(patch); PatchCreator patchCreator = null; + RandomAccessFile firstRandom = null, secondRandom = null, output = null; try { - patchCreator = linear ? new LinearCreator(first, second, patch) : null; + firstRandom = new RandomAccessFile(first, "r"); + secondRandom = new RandomAccessFile(second, "r"); + output = new RandomAccessFile(patch, "rw"); } catch (FileNotFoundException e) { System.err.println("Unable to open file: " + e.getMessage()); e.printStackTrace(); System.exit(1); } try { + long firstLength = firstRandom.length(); + long secondLength = secondRandom.length(); + ByteBuffer firstBuffer = firstRandom.getChannel().map(MapMode.READ_ONLY, 0, firstLength); + ByteBuffer secondBuffer = secondRandom.getChannel().map(MapMode.READ_ONLY, 0, secondLength); + ByteBuffer outputBuffer = ByteBuffer.allocate((int)Math.ceil(Math.max(firstLength, secondLength) * 1.5)); + patchCreator = linear ? new LinearCreator(firstBuffer, firstLength, secondBuffer, secondLength, outputBuffer) : null; + output.write(outputBuffer.array()); + output.close(); patchCreator.create(); } catch (Exception e) { System.err.println("Unable to create patch of " + first.getName() + " and " + second.getName()); e.printStackTrace(); System.exit(1); + } finally { + try { + if (firstRandom != null) firstRandom.close(); + if (secondRandom != null) secondRandom.close();; + } catch (IOException e) { + System.err.println("Unable to close files"); + } } System.out.println("Successfuly created patch of " + first.getName() + " and " + second.getName() + " in " + patch.getName()); } diff --git a/src/main/java/net/md_5/jbeat/PatchCreator.java b/src/main/java/net/md_5/jbeat/PatchCreator.java old mode 100644 new mode 100755 index 0c089c7..2c3db79 --- a/src/main/java/net/md_5/jbeat/PatchCreator.java +++ b/src/main/java/net/md_5/jbeat/PatchCreator.java @@ -32,6 +32,7 @@ import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.FileChannel; + import static net.md_5.jbeat.Shared.*; /** @@ -40,37 +41,25 @@ abstract class PatchCreator { /** - * The clean, unmodified file. - */ - protected final RandomAccessFile sourceFile; - /** - * The source file mapped into memory. + * The source mapped into memory. */ protected ByteBuffer source; /** - * Length of the source file. + * Length of the source. */ protected long sourceLength; /** - * The modified file which we will difference with the source file. - */ - protected final RandomAccessFile targetFile; - /** - * The target file mapped into memory. + * The target mapped into memory. */ protected ByteBuffer target; /** - * Length of the target file. + * Length of the target. */ protected long targetLength; /** * The location to which the patch will be generated. */ - protected final File outFile; - /** - * Stream to the patch output. - */ - protected final OutputStream out; + protected final ByteBuffer out; /** * UTF-8, optional patch header. */ @@ -80,101 +69,86 @@ abstract class PatchCreator { * Creates a new beat patch creator instance. In order to create and output * the patch the {@link #create()} method must be called. * - * @param original file, which the patch applicator will have access to - * @param modified file which has been changed from the original + * @param source the original bytes to generate the patch from + * @param sourceLength, the number of bytes in the source + * @param modified the modified bytes which has been changed from the original + * @param modifiedLength the number of modified bytes * @param output location to which the patch will be output * @param header to be used as beat metadata - * @throws FileNotFoundException when one of the files cannot be opened for - * read or write access */ - protected PatchCreator(File original, File modified, File output, String header) throws FileNotFoundException { - this.sourceFile = new RandomAccessFile(original, "r"); - this.targetFile = new RandomAccessFile(modified, "r"); - this.out = new BufferedOutputStream(new FileOutputStream(output)); - this.outFile = output; + protected PatchCreator(ByteBuffer source, long sourceLength, ByteBuffer modified, long modifiedLength, ByteBuffer output, String header) { + this.source = source; + this.sourceLength = sourceLength; + this.target = modified; + this.targetLength = modifiedLength; + this.out = output; this.header = header; } - protected PatchCreator(File original, File modified, File output) throws FileNotFoundException { - this(original, modified, output, null); + protected PatchCreator(ByteBuffer source, long sourceLength, ByteBuffer modified, long modifiedLength, ByteBuffer output) { + this(source, sourceLength, modified, modifiedLength, output, null); } /** * Creates a beat version 1 format binary patch of the two files specified - * in the contrstructor. This method will header the file with beat + * in the constructor. This method will header the file with beat * information, delegate binary differencing to the specific patch style * implementation, and then finish the patch with the various checksums * before writing to disk. */ public void create() throws IOException { - try { - // store file lengths - sourceLength = sourceFile.length(); - targetLength = targetFile.length(); - // map the files - source = sourceFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, sourceLength); - target = targetFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, targetLength); - // write header - for (char c : magicHeader) { - out.write(c); - } - // write original size - encode(out, sourceLength); - // write modified size - encode(out, targetLength); - // write header length - int headerLength = (header == null) ? 0 : header.length(); - encode(out, headerLength); - // write the header - if (header != null) { - ByteBuffer encoded = encoder.encode(CharBuffer.wrap(header)); - out.write(encoded.array(), encoded.arrayOffset(), encoded.limit()); - } - // do the actual patch - doPatch(); - // write original checksum - writeIntLE(out, (int) checksum(source, sourceLength)); - // write target checksum - writeIntLE(out, (int) checksum(target, targetLength)); - // map ourselves to ram - out.flush(); - // store patch length - long outLength = outFile.length(); - ByteBuffer self = new RandomAccessFile(outFile, "rw").getChannel().map(FileChannel.MapMode.READ_ONLY, 0, outLength); - // write self checksum - writeIntLE(out, (int) checksum(self, outLength)); - } finally { - // close the streams - sourceFile.close(); - targetFile.close(); - out.close(); + // write header + for (char c : magicHeader) { + out.putChar(c); + } + // write original size + encode(out, sourceLength); + // write modified size + encode(out, targetLength); + // write header length + int headerLength = (header == null) ? 0 : header.length(); + encode(out, headerLength); + // write the header + if (header != null) { + ByteBuffer encoded = encoder.encode(CharBuffer.wrap(header)); + out.put(encoded.array(), encoded.arrayOffset(), encoded.limit()); } + // do the actual patch + doPatch(); + // write original checksum + writeIntLE(out, (int) checksum(source, sourceLength)); + // write target checksum + writeIntLE(out, (int) checksum(target, targetLength)); + // store patch length + long outLength = out.position(); + // write self checksum + writeIntLE(out, (int) checksum((ByteBuffer) out.asReadOnlyBuffer().position(0), outLength)); } /** * Writes and integer to the specified output stream in it's little Endian * form. This method does not & with 0xFF and should not need to. */ - private void writeIntLE(OutputStream out, int value) throws IOException { - out.write(value); - out.write(value >> 8); - out.write(value >> 16); - out.write(value >> 24); + private void writeIntLE(ByteBuffer out, int value) throws IOException { + out.putInt(value); + out.putInt(value >> 8); + out.putInt(value >> 16); + out.putInt(value >> 24); } /** * Encode a single number as into it's variable length form and write it to * the output stream. */ - protected final void encode(OutputStream out, long data) throws IOException { + protected final void encode(ByteBuffer out, long data) throws IOException { while (true) { long x = data & 0x7f; data >>= 7; if (data == 0) { - out.write((byte) (0x80 | x)); + out.put((byte) (0x80 | x)); break; } - out.write((byte) x); + out.put((byte) x); data--; } } From 91f7e906716772915fd0e51cc9cf8a83488f84e7 Mon Sep 17 00:00:00 2001 From: Techcable Date: Sun, 5 Jul 2015 20:09:08 -0700 Subject: [PATCH 8/9] Allow the Patcher to use input other than a RandomAccessFile --- src/main/java/net/md_5/jbeat/Main.java | 29 +++- src/main/java/net/md_5/jbeat/Patcher.java | 160 ++++++++++------------ 2 files changed, 102 insertions(+), 87 deletions(-) mode change 100644 => 100755 src/main/java/net/md_5/jbeat/Patcher.java diff --git a/src/main/java/net/md_5/jbeat/Main.java b/src/main/java/net/md_5/jbeat/Main.java index 6a746c6..167f813 100755 --- a/src/main/java/net/md_5/jbeat/Main.java +++ b/src/main/java/net/md_5/jbeat/Main.java @@ -28,6 +28,7 @@ import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel.MapMode; +import java.util.Random; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; @@ -101,14 +102,38 @@ public static void patch(File in, File patch, File out) { System.exit(1); } createFile(out); - + + RandomAccessFile inRandom = null, patchRandom = null, outRandom = null; + try { + inRandom = new RandomAccessFile(in, "r"); + patchRandom = new RandomAccessFile(patch, "r"); + outRandom = new RandomAccessFile(out, "rw"); + } catch (FileNotFoundException ex) { + System.err.println("No such file: " + ex.getMessage()); + ex.printStackTrace(); + System.exit(1); + } try { - Patcher patcher = new Patcher(patch, in, out); + ByteBuffer inBytes = inRandom.getChannel().map(MapMode.READ_ONLY, 0, inRandom.length()); + ByteBuffer patchBytes = patchRandom.getChannel().map(MapMode.READ_ONLY, 0, patchRandom.length()); + ByteBuffer outBytes = ByteBuffer.allocate((int) Math.ceil((inRandom.length() + patchRandom.length()) * 1.5)); + Patcher patcher = new Patcher(patchBytes, patchRandom.length(), inBytes, outBytes); + outRandom.write(outBytes.array()); patcher.patch(); } catch (Exception e) { System.err.println("Unable to patch file " + in.getName()); e.printStackTrace(); System.exit(1); + } finally { + try { + if (inRandom != null) inRandom.close(); + if (patchRandom != null) patchRandom.close(); + if (outRandom != null) outRandom.close(); + } catch (IOException e) { + System.err.println("Unable to close: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } } System.out.println("Successfuly Patched: " + in.getName() + " with " + patch.getName()); diff --git a/src/main/java/net/md_5/jbeat/Patcher.java b/src/main/java/net/md_5/jbeat/Patcher.java old mode 100644 new mode 100755 index d0fcd10..9a5d33f --- a/src/main/java/net/md_5/jbeat/Patcher.java +++ b/src/main/java/net/md_5/jbeat/Patcher.java @@ -22,13 +22,13 @@ */ package net.md_5.jbeat; -import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; + import static net.md_5.jbeat.Shared.*; /** @@ -39,116 +39,106 @@ public final class Patcher { /** * The patch which we will get our instructions from. */ - private final RandomAccessFile patchFile; + private final ByteBuffer patch; + /** + * The length of the patch bytes + */ + private final long patchLength; /** - * The clean, unmodified file. This must be the same file from which the + * The clean, unmodified bytes. This must be the same bytes from which the * patch was generated. */ - private final RandomAccessFile sourceFile; + private final ByteBuffer source; /** - * The location to which the new, patched file will be output. + * The location to which the new, patched bytes will be output. */ - private final RandomAccessFile targetFile; + private final ByteBuffer target; /** * Create a new beat patcher instance. In order to complete the patch * process {@link #patch()} method must be called. * - * @param patchFile the beat format patch file - * @param sourceFile original file from which the patch was created - * @param targetFile location to which the new, patched file will be output + * @param patch the beat format patch + * @param patchLength the length of the patch + * @param source original from which the patch was created + * @param target location to which the new, patched bytes will be output + * * @throws FileNotFoundException when one of the files cannot be opened for * read or write access */ - public Patcher(File patchFile, File sourceFile, File targetFile) throws FileNotFoundException { - this.patchFile = new RandomAccessFile(patchFile, "r"); - this.sourceFile = new RandomAccessFile(sourceFile, "r"); - this.targetFile = new RandomAccessFile(targetFile, "rw"); + public Patcher(ByteBuffer patch, long patchLength, ByteBuffer source, ByteBuffer target) throws FileNotFoundException { + this.patch = patch; + this.patchLength = patchLength; + this.source = source; + this.target = target; } /** * The meat of the program, patches everything. All logic goes here. */ public void patch() throws IOException { - try { - // store patch length - long patchLength = patchFile.length(); - // map patch file into memory - ByteBuffer patch = patchFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, patchLength); - // check the header - for (char c : magicHeader) { - if (patch.get() != c) { - throw new IOException("Patch file does not contain correct BPS header!"); - } + // check the header + for (char c : magicHeader) { + if (patch.get() != c) { + throw new IOException("Patch file does not contain correct BPS header!"); } - // read source size - long sourceSize = decode(patch); - // map as much of the source file as we need into memory - ByteBuffer source = sourceFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, sourceSize); - // read target size - long targetSize = decode(patch); - // expand the target file - targetFile.setLength(targetSize); - // map a large enough chunk of the target into memory - ByteBuffer target = targetFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, targetSize); - // read metadata - String metadata = readString(patch); - // store last offsets - int sourceOffset = 0, targetOffset = 0; - // do the actual patching - while (patch.position() < patchLength - 12) { - long length = decode(patch); - long mode = length & 3; - length = (length >> 2) + 1; - // branch per mode - if (mode == SOURCE_READ) { - while (length-- != 0) { - target.put(source.get(target.position())); - } - } else if (mode == TARGET_READ) { + } + // read source size + long sourceSize = decode(patch); + // read target size + long targetSize = decode(patch); + // read metadata + String metadata = readString(patch); + // store last offsets + int sourceOffset = 0, targetOffset = 0; + // do the actual patching + while (patch.position() < patchLength - 12) { + long length = decode(patch); + long mode = length & 3; + length = (length >> 2) + 1; + // branch per mode + if (mode == SOURCE_READ) { + while (length-- != 0) { + target.put(source.get(target.position())); + } + } else if (mode == TARGET_READ) { + while (length-- != 0) { + target.put(patch.get()); + } + } else { + // start the same + long data = decode(patch); + long offset = (((data & 1) != 0) ? -1 : 1) * (data >> 1); + // descend deeper + if (mode == SOURCE_COPY) { + sourceOffset += offset; while (length-- != 0) { - target.put(patch.get()); + target.put(source.get(sourceOffset++)); } } else { - // start the same - long data = decode(patch); - long offset = (((data & 1) != 0) ? -1 : 1) * (data >> 1); - // descend deeper - if (mode == SOURCE_COPY) { - sourceOffset += offset; - while (length-- != 0) { - target.put(source.get(sourceOffset++)); - } - } else { - targetOffset += offset; - while (length-- != 0) { - target.put(target.get(targetOffset++)); - } + targetOffset += offset; + while (length-- != 0) { + target.put(target.get(targetOffset++)); } } } - // flip to little endian mode - patch.order(ByteOrder.LITTLE_ENDIAN); - // checksum of the source - long sourceChecksum = readInt(patch); - if (checksum(source, sourceSize) != sourceChecksum) { - throw new IOException("Source checksum does not match!"); - } - // checksum of the target - long targetChecksum = readInt(patch); - if (checksum(target, targetSize) != targetChecksum) { - throw new IOException("Target checksum does not match!"); - } - // checksum of the patch itself - long patchChecksum = readInt(patch); - if (checksum(patch, patchLength - 4) != patchChecksum) { - throw new IOException("Patch checksum does not match!"); - } - } finally { - // close the streams - patchFile.close(); - sourceFile.close(); - targetFile.close(); + } + // flip to little endian mode + patch.order(ByteOrder.LITTLE_ENDIAN); + // checksum of the source + long sourceChecksum = readInt(patch); + if (checksum(source, sourceSize) != sourceChecksum) { + throw new IOException("Source checksum does not match!"); + } + // checksum of the target + long targetChecksum = readInt(patch); + if (checksum(target, targetSize) != targetChecksum) { + throw new IOException("Target checksum does not match!"); + } + // checksum of the patch itself + long patchChecksum = readInt(patch); + if (checksum(patch, patchLength - 4) != patchChecksum) { + throw new IOException("Patch checksum does not match!"); } } From ba8cd4d1524495059988099b31a0728a3ddc7ea3 Mon Sep 17 00:00:00 2001 From: Techcable Date: Tue, 7 Jul 2015 17:58:54 -0700 Subject: [PATCH 9/9] Add a ByteBuffer abstraction to make it easier for sources with an unkown size Format --- .../java/net/md_5/jbeat/LinearCreator.java | 19 +- src/main/java/net/md_5/jbeat/Main.java | 48 ++--- .../java/net/md_5/jbeat/PatchCreator.java | 45 +++-- src/main/java/net/md_5/jbeat/Patcher.java | 73 +++----- src/main/java/net/md_5/jbeat/Shared.java | 9 +- .../net/md_5/jbeat/util/ByteArrayList.java | 85 +++++++++ .../java/net/md_5/jbeat/util/ByteBuf.java | 166 ++++++++++++++++++ .../java/net/md_5/jbeat/util/ByteBufs.java | 81 +++++++++ .../java/net/md_5/jbeat/util/ByteList.java | 38 ++++ .../java/net/md_5/jbeat/util/ByteLists.java | 65 +++++++ .../java/net/md_5/jbeat/util/NIOByteBuf.java | 132 ++++++++++++++ .../net/md_5/jbeat/util/ResizableByteBuf.java | 119 +++++++++++++ 12 files changed, 774 insertions(+), 106 deletions(-) mode change 100644 => 100755 src/main/java/net/md_5/jbeat/Shared.java create mode 100755 src/main/java/net/md_5/jbeat/util/ByteArrayList.java create mode 100755 src/main/java/net/md_5/jbeat/util/ByteBuf.java create mode 100755 src/main/java/net/md_5/jbeat/util/ByteBufs.java create mode 100755 src/main/java/net/md_5/jbeat/util/ByteList.java create mode 100755 src/main/java/net/md_5/jbeat/util/ByteLists.java create mode 100755 src/main/java/net/md_5/jbeat/util/NIOByteBuf.java create mode 100755 src/main/java/net/md_5/jbeat/util/ResizableByteBuf.java diff --git a/src/main/java/net/md_5/jbeat/LinearCreator.java b/src/main/java/net/md_5/jbeat/LinearCreator.java index fc29717..d3c1041 100755 --- a/src/main/java/net/md_5/jbeat/LinearCreator.java +++ b/src/main/java/net/md_5/jbeat/LinearCreator.java @@ -22,10 +22,9 @@ */ package net.md_5.jbeat; -import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; -import java.nio.ByteBuffer; + +import net.md_5.jbeat.util.ByteBuf; import static net.md_5.jbeat.Shared.*; @@ -37,28 +36,28 @@ public final class LinearCreator extends PatchCreator { private int targetReadLength, targetRelativeOffset, outputOffset; - public LinearCreator(ByteBuffer source, long sourceLength, ByteBuffer modified, long modifiedLength, ByteBuffer output, String header) { + public LinearCreator(ByteBuf source, long sourceLength, ByteBuf modified, long modifiedLength, ByteBuf output, String header) { super(source, sourceLength, modified, modifiedLength, output, header); } - public LinearCreator(ByteBuffer source, long sourceLength, ByteBuffer modified, long modifiedLength, ByteBuffer output) { + public LinearCreator(ByteBuf source, long sourceLength, ByteBuf modified, long modifiedLength, ByteBuf output) { super(source, sourceLength, modified, modifiedLength, output, null); } @Override protected void doPatch() throws IOException { - while (outputOffset < target.limit()) { + while (outputOffset < target.readableBytes()) { int sourcePos = 0; for (int n = 0; outputOffset + n < Math.min(sourceLength, targetLength); n++) { - if (source.get(outputOffset + n) != target.get(outputOffset + n)) { + if (source.read(outputOffset + n) != target.read(outputOffset + n)) { break; } sourcePos++; } int rleLength = 0; - for (int n = 1; outputOffset + n < target.limit(); n++) { - if (target.get(outputOffset) != target.get(outputOffset + n)) { + for (int n = 1; outputOffset + n < target.readableBytes(); n++) { + if (target.read(outputOffset) != target.read(outputOffset + n)) { break; } rleLength++; @@ -96,7 +95,7 @@ private void targetReadFlush() throws IOException { encode(out, TARGET_READ | ((targetReadLength - 1) << 2)); int offset = outputOffset - targetReadLength; while (targetReadLength != 0) { - out.put(target.get(offset++)); + out.write(target.read(offset++)); targetReadLength--; } } diff --git a/src/main/java/net/md_5/jbeat/Main.java b/src/main/java/net/md_5/jbeat/Main.java index 167f813..06f3e82 100755 --- a/src/main/java/net/md_5/jbeat/Main.java +++ b/src/main/java/net/md_5/jbeat/Main.java @@ -22,21 +22,23 @@ */ package net.md_5.jbeat; +import lombok.*; + import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; -import java.nio.ByteBuffer; import java.nio.channels.FileChannel.MapMode; -import java.util.Random; + +import net.md_5.jbeat.util.ByteBuf; +import net.md_5.jbeat.util.ByteBufs; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.converters.FileConverter; -import lombok.*; - public class Main { + public static void main(String[] rawArgs) { JBeatOptions options = new JBeatOptions(); new JCommander(options, rawArgs); @@ -50,7 +52,7 @@ public static void main(String[] rawArgs) { System.exit(1); } } - + public static void createPatch(File first, File second, File patch, boolean linear) { if (patch.exists()) { System.err.println("The patch file " + patch.getName() + " exists."); @@ -73,9 +75,9 @@ public static void createPatch(File first, File second, File patch, boolean line try { long firstLength = firstRandom.length(); long secondLength = secondRandom.length(); - ByteBuffer firstBuffer = firstRandom.getChannel().map(MapMode.READ_ONLY, 0, firstLength); - ByteBuffer secondBuffer = secondRandom.getChannel().map(MapMode.READ_ONLY, 0, secondLength); - ByteBuffer outputBuffer = ByteBuffer.allocate((int)Math.ceil(Math.max(firstLength, secondLength) * 1.5)); + ByteBuf firstBuffer = ByteBufs.wrap(firstRandom.getChannel().map(MapMode.READ_ONLY, 0, firstLength)); + ByteBuf secondBuffer = ByteBufs.wrap(secondRandom.getChannel().map(MapMode.READ_ONLY, 0, secondLength)); + ByteBuf outputBuffer = ByteBufs.create(); patchCreator = linear ? new LinearCreator(firstBuffer, firstLength, secondBuffer, secondLength, outputBuffer) : null; output.write(outputBuffer.array()); output.close(); @@ -87,14 +89,15 @@ public static void createPatch(File first, File second, File patch, boolean line } finally { try { if (firstRandom != null) firstRandom.close(); - if (secondRandom != null) secondRandom.close();; + if (secondRandom != null) secondRandom.close(); + ; } catch (IOException e) { System.err.println("Unable to close files"); } } System.out.println("Successfuly created patch of " + first.getName() + " and " + second.getName() + " in " + patch.getName()); } - + public static void patch(File in, File patch, File out) { if (out.exists()) { System.err.println("The output file " + out.getName() + " exists."); @@ -114,12 +117,12 @@ public static void patch(File in, File patch, File out) { System.exit(1); } try { - ByteBuffer inBytes = inRandom.getChannel().map(MapMode.READ_ONLY, 0, inRandom.length()); - ByteBuffer patchBytes = patchRandom.getChannel().map(MapMode.READ_ONLY, 0, patchRandom.length()); - ByteBuffer outBytes = ByteBuffer.allocate((int) Math.ceil((inRandom.length() + patchRandom.length()) * 1.5)); + ByteBuf inBytes = ByteBufs.wrap(inRandom.getChannel().map(MapMode.READ_ONLY, 0, inRandom.length())); + ByteBuf patchBytes = ByteBufs.wrap(patchRandom.getChannel().map(MapMode.READ_ONLY, 0, patchRandom.length())); + ByteBuf outBytes = ByteBufs.create((int) inRandom.length()); Patcher patcher = new Patcher(patchBytes, patchRandom.length(), inBytes, outBytes); - outRandom.write(outBytes.array()); patcher.patch(); + outRandom.write(outBytes.array()); } catch (Exception e) { System.err.println("Unable to patch file " + in.getName()); e.printStackTrace(); @@ -135,10 +138,10 @@ public static void patch(File in, File patch, File out) { System.exit(1); } } - + System.out.println("Successfuly Patched: " + in.getName() + " with " + patch.getName()); } - + private static void createFile(File f) { try { f.createNewFile(); @@ -148,24 +151,25 @@ private static void createFile(File f) { System.exit(1); } } - + @Getter public static class JBeatOptions { + @Parameter(names = {"-a", "--first-file"}, description = "Original file for generating patch", converter = FileConverter.class) private File firstFile; - + @Parameter(names = {"-b", "--second-file"}, description = "Changed file for generating patch", converter = FileConverter.class) private File secondFile; - + @Parameter(names = {"--patch-out"}, description = "The file to put the generated patch in", converter = FileConverter.class) private File patchOut = new File("output.bps"); - + @Parameter(names = {"-i", "--in-file"}, description = "Input file to patch", converter = FileConverter.class) private File inFile; - + @Parameter(names = {"-o", "--out-file"}, description = "Write patched file here", converter = FileConverter.class) private File outFile; - + @Parameter(names = {"-p", "--patch-in"}, description = "Patch to use to patch in file", converter = FileConverter.class) private File patchIn; diff --git a/src/main/java/net/md_5/jbeat/PatchCreator.java b/src/main/java/net/md_5/jbeat/PatchCreator.java index 2c3db79..c4068a4 100755 --- a/src/main/java/net/md_5/jbeat/PatchCreator.java +++ b/src/main/java/net/md_5/jbeat/PatchCreator.java @@ -22,16 +22,11 @@ */ package net.md_5.jbeat; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; -import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.CharBuffer; -import java.nio.channels.FileChannel; + +import net.md_5.jbeat.util.ByteBuf; import static net.md_5.jbeat.Shared.*; @@ -43,7 +38,7 @@ abstract class PatchCreator { /** * The source mapped into memory. */ - protected ByteBuffer source; + protected ByteBuf source; /** * Length of the source. */ @@ -51,7 +46,7 @@ abstract class PatchCreator { /** * The target mapped into memory. */ - protected ByteBuffer target; + protected ByteBuf target; /** * Length of the target. */ @@ -59,7 +54,7 @@ abstract class PatchCreator { /** * The location to which the patch will be generated. */ - protected final ByteBuffer out; + protected final ByteBuf out; /** * UTF-8, optional patch header. */ @@ -76,7 +71,7 @@ abstract class PatchCreator { * @param output location to which the patch will be output * @param header to be used as beat metadata */ - protected PatchCreator(ByteBuffer source, long sourceLength, ByteBuffer modified, long modifiedLength, ByteBuffer output, String header) { + protected PatchCreator(ByteBuf source, long sourceLength, ByteBuf modified, long modifiedLength, ByteBuf output, String header) { this.source = source; this.sourceLength = sourceLength; this.target = modified; @@ -85,7 +80,7 @@ protected PatchCreator(ByteBuffer source, long sourceLength, ByteBuffer modified this.header = header; } - protected PatchCreator(ByteBuffer source, long sourceLength, ByteBuffer modified, long modifiedLength, ByteBuffer output) { + protected PatchCreator(ByteBuf source, long sourceLength, ByteBuf modified, long modifiedLength, ByteBuf output) { this(source, sourceLength, modified, modifiedLength, output, null); } @@ -99,7 +94,7 @@ protected PatchCreator(ByteBuffer source, long sourceLength, ByteBuffer modified public void create() throws IOException { // write header for (char c : magicHeader) { - out.putChar(c); + out.write((byte) c); } // write original size encode(out, sourceLength); @@ -111,7 +106,7 @@ public void create() throws IOException { // write the header if (header != null) { ByteBuffer encoded = encoder.encode(CharBuffer.wrap(header)); - out.put(encoded.array(), encoded.arrayOffset(), encoded.limit()); + out.write(encoded.array(), encoded.arrayOffset(), encoded.limit()); } // do the actual patch doPatch(); @@ -120,35 +115,37 @@ public void create() throws IOException { // write target checksum writeIntLE(out, (int) checksum(target, targetLength)); // store patch length - long outLength = out.position(); + long outLength = out.getPosition(); // write self checksum - writeIntLE(out, (int) checksum((ByteBuffer) out.asReadOnlyBuffer().position(0), outLength)); + ByteBuf duplicated = out.duplicate(); + duplicated.reset(); + writeIntLE(out, (int) checksum(duplicated, outLength)); } /** * Writes and integer to the specified output stream in it's little Endian * form. This method does not & with 0xFF and should not need to. */ - private void writeIntLE(ByteBuffer out, int value) throws IOException { - out.putInt(value); - out.putInt(value >> 8); - out.putInt(value >> 16); - out.putInt(value >> 24); + private void writeIntLE(ByteBuf out, int value) throws IOException { + out.write((byte) value); + out.write((byte) (value >> 8)); + out.write((byte) (value >> 16)); + out.write((byte) (value >> 24)); } /** * Encode a single number as into it's variable length form and write it to * the output stream. */ - protected final void encode(ByteBuffer out, long data) throws IOException { + protected final void encode(ByteBuf out, long data) throws IOException { while (true) { long x = data & 0x7f; data >>= 7; if (data == 0) { - out.put((byte) (0x80 | x)); + out.write((byte) (0x80 | x)); break; } - out.put((byte) x); + out.write((byte) x); data--; } } diff --git a/src/main/java/net/md_5/jbeat/Patcher.java b/src/main/java/net/md_5/jbeat/Patcher.java index 9a5d33f..4b210ff 100755 --- a/src/main/java/net/md_5/jbeat/Patcher.java +++ b/src/main/java/net/md_5/jbeat/Patcher.java @@ -24,10 +24,11 @@ import java.io.FileNotFoundException; import java.io.IOException; -import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.nio.channels.FileChannel; + +import net.md_5.jbeat.util.ByteBuf; +import net.md_5.jbeat.util.ByteBufs; import static net.md_5.jbeat.Shared.*; @@ -39,7 +40,7 @@ public final class Patcher { /** * The patch which we will get our instructions from. */ - private final ByteBuffer patch; + private ByteBuf patch; /** * The length of the patch bytes */ @@ -48,11 +49,11 @@ public final class Patcher { * The clean, unmodified bytes. This must be the same bytes from which the * patch was generated. */ - private final ByteBuffer source; + private final ByteBuf source; /** * The location to which the new, patched bytes will be output. */ - private final ByteBuffer target; + private final ByteBuf target; /** * Create a new beat patcher instance. In order to complete the patch @@ -66,7 +67,7 @@ public final class Patcher { * @throws FileNotFoundException when one of the files cannot be opened for * read or write access */ - public Patcher(ByteBuffer patch, long patchLength, ByteBuffer source, ByteBuffer target) throws FileNotFoundException { + public Patcher(ByteBuf patch, long patchLength, ByteBuf source, ByteBuf target) throws FileNotFoundException { this.patch = patch; this.patchLength = patchLength; this.source = source; @@ -79,52 +80,56 @@ public Patcher(ByteBuffer patch, long patchLength, ByteBuffer source, ByteBuffer public void patch() throws IOException { // check the header for (char c : magicHeader) { - if (patch.get() != c) { + if (patch.read() != c) { throw new IOException("Patch file does not contain correct BPS header!"); } } // read source size - long sourceSize = decode(patch); + long sourceSize = patch.readVarLong(); // read target size - long targetSize = decode(patch); + long targetSize = patch.readVarLong(); // read metadata String metadata = readString(patch); // store last offsets int sourceOffset = 0, targetOffset = 0; // do the actual patching - while (patch.position() < patchLength - 12) { - long length = decode(patch); + while (patch.getPosition() < patchLength - 12) { + long length = patch.readVarLong(); long mode = length & 3; length = (length >> 2) + 1; // branch per mode if (mode == SOURCE_READ) { while (length-- != 0) { - target.put(source.get(target.position())); + target.write(source.read((int) target.getPosition())); } } else if (mode == TARGET_READ) { while (length-- != 0) { - target.put(patch.get()); + target.write(patch.read()); } } else { // start the same - long data = decode(patch); + long data = patch.readVarLong(); long offset = (((data & 1) != 0) ? -1 : 1) * (data >> 1); // descend deeper if (mode == SOURCE_COPY) { sourceOffset += offset; while (length-- != 0) { - target.put(source.get(sourceOffset++)); + target.write(source.read(sourceOffset++)); } } else { targetOffset += offset; while (length-- != 0) { - target.put(target.get(targetOffset++)); + target.write(target.read(targetOffset++)); } } } } // flip to little endian mode - patch.order(ByteOrder.LITTLE_ENDIAN); + try { + patch.setOrder(ByteOrder.LITTLE_ENDIAN); + } catch (UnsupportedOperationException e) { + patch = ByteBufs.wrap(ByteBuffer.wrap(patch.array()).order(ByteOrder.LITTLE_ENDIAN)); + } // checksum of the source long sourceChecksum = readInt(patch); if (checksum(source, sourceSize) != sourceChecksum) { @@ -146,39 +151,15 @@ public void patch() throws IOException { * Read a UTF-8 string with variable length number length descriptor. Will * return null if there is no data read, or the string is of 0 length. */ - private String readString(ByteBuffer in) throws IOException { - int length = (int) decode(in); - String ret = null; - if (length != 0) { - int limit = in.limit(); - in.limit(in.position() + length); - ret = charset.decode(in).toString(); - in.limit(limit); - } - return ret; + private String readString(ByteBuf in) throws IOException { + String s = in.readString(); + return s.length() == 0 ? null : s; } /** * Read a set of bytes from a buffer return them as a unsigned integer. */ - private long readInt(ByteBuffer in) throws IOException { - return in.getInt() & 0xFFFFFFFFL; - } - - /** - * Read a single variable length number from the input stream. - */ - private long decode(ByteBuffer in) throws IOException { - long data = 0, shift = 1; - while (true) { - byte x = in.get(); - data += (x & 0x7F) * shift; - if ((x & 0x80) != 0x00) { - break; - } - shift <<= 7; - data += shift; - } - return data; + private long readInt(ByteBuf in) throws IOException { + return in.readInt() & 0xFFFFFFFFL; } } diff --git a/src/main/java/net/md_5/jbeat/Shared.java b/src/main/java/net/md_5/jbeat/Shared.java old mode 100644 new mode 100755 index 16a4e52..8f0a2db --- a/src/main/java/net/md_5/jbeat/Shared.java +++ b/src/main/java/net/md_5/jbeat/Shared.java @@ -22,12 +22,13 @@ */ package net.md_5.jbeat; -import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.util.zip.CRC32; +import net.md_5.jbeat.util.ByteBuf; + /** * Class containing methods common to both beat patch creators and applicators. */ @@ -74,11 +75,11 @@ final class Shared { * method is destructive and will call {@link java.nio.Buffer#rewind()}, * thus discarding the current mark and position. */ - static long checksum(ByteBuffer in, long length) { + static long checksum(ByteBuf in, long length) { CRC32 crc = new CRC32(); byte[] back = new byte[(int) length]; - in.rewind(); - in.get(back); + in.reset(); + in.read(back); crc.update(back); return crc.getValue(); } diff --git a/src/main/java/net/md_5/jbeat/util/ByteArrayList.java b/src/main/java/net/md_5/jbeat/util/ByteArrayList.java new file mode 100755 index 0000000..736f270 --- /dev/null +++ b/src/main/java/net/md_5/jbeat/util/ByteArrayList.java @@ -0,0 +1,85 @@ +/** + * The MIT License + * Copyright (c) 2015 Techcable + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package net.md_5.jbeat.util; + +import java.util.Arrays; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +class ByteArrayList implements ByteList { + + private final ReadWriteLock bytesLock = new ReentrantReadWriteLock(); + private byte[] bytes; + private int length; + + private static final int DEFAULT_CAPACITY = 20; + + public ByteArrayList() { + this(DEFAULT_CAPACITY); + } + + public ByteArrayList(int initialCapacity) { + bytes = new byte[initialCapacity]; + } + + public void add(byte b) { + grow(length + 1); + bytesLock.readLock().lock(); + try { + bytes[length] = b; + } finally { + bytesLock.readLock().unlock(); + } + length++; + } + + public byte get(int index) { + return bytes[index]; + } + + public void addAll(byte[] bytes) { + addAll(bytes, 0, bytes.length); + } + + public void addAll(final byte[] bytes, int start, final int end) { + grow(length + bytes.length); + for (; start < end; start++) { + add(bytes[start]); + } + } + + public static final int MINIMUM_GROW = 20; + + private void grow(int newLength) { + if (length >= newLength) return; + bytes = Arrays.copyOf(bytes, newLength + MINIMUM_GROW); + } + + public byte[] toArray() { + return Arrays.copyOf(bytes, length); + } + + public int size() { + return length; + } +} diff --git a/src/main/java/net/md_5/jbeat/util/ByteBuf.java b/src/main/java/net/md_5/jbeat/util/ByteBuf.java new file mode 100755 index 0000000..7d14156 --- /dev/null +++ b/src/main/java/net/md_5/jbeat/util/ByteBuf.java @@ -0,0 +1,166 @@ +/** + * The MIT License + * Copyright (c) 2015 Techcable + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package net.md_5.jbeat.util; + +import java.nio.ByteOrder; + +public interface ByteBuf { + + /** + * Get the array that currently backs this byte buffer + *

+ * Modifications to the backing array may or may not be reflected in the byte buf + * Modifications to the byte buffer may or may not be reflected in the array + *

+ * + * @return the array that backs this byte buffer + * + * @throws java.lang.UnsupportedOperationException if an array doesn't back this byte buffer + */ + public byte[] array(); + + /** + * Reads the byte at the buffers current position, and then increments the position + * + * @return the byte at the buffer's current position + * + * @throws java.lang.IndexOutOfBoundsException if there are not enough bytes in the buffer + */ + public byte read(); + + /** + * Reads the next four bytes in the buffer and turns them into an int, and then increments the position by 4 + * + * @return the integer at the buffer's current position + * + * @throws java.lang.IndexOutOfBoundsException if there are not enough bytes in the buffer + */ + public int readInt(); + + /** + * Reads the byte at the specified position + * + * @param index where to read from + * + * @return the byte at the buffer's current position + * + * @throws java.lang.IndexOutOfBoundsException if the index is past the readable bytes + */ + public byte read(int index); + + /** + * Read the contents of the buffer into the specified array + * + * @param bytes the array to fill + * + * @throws java.lang.IndexOutOfBoundsException if the buffer runs out of bytes mid-copy + */ + public void read(byte[] bytes); + + /** + * Write the contents of the array into the buffer, starting at the specified offset in the source array, and reading "length" bytes + * + * @param bytes the array to read from + * @param offset where to start copying + * @param length how much to read from the array + * + * @throws java.lang.IndexOutOfBoundsException if the buffer is bounded, and the next byte is past capacity + * @throws java.lang.IllegalArgumentException if offset is larger than array.length or negative, or length is negative or larger than array.length - offset + */ + public void write(byte[] bytes, int offset, int length); + + /** + * Add the byte to the end of the buffer + * + * @param b the byte to write + * + * @throws java.lang.IndexOutOfBoundsException if the buffer is bounded, and the next byte is past capacity + */ + public void write(byte b); + + /** + * Write the four bytes in the specified int to the end of the buffer + * + * @param i the integer to write + * + * @throws java.lang.IndexOutOfBoundsException if the buffer is bounded, and the next byte is past capacity + */ + public void writeInt(int i); + + /** + * Gets the current position of the ByteBuf + * + * @return the current position of the byte buf + * + * @throws java.lang.IndexOutOfBoundsException if the index is past the readable bytes of the buffer + */ + public long getPosition(); + + /** + * Reset the ByteBuf's position to zero + */ + public void reset(); + + /** + * Read a variable length long from the stream, incrementing position the number of bytes specified by the integer + * + * @return a variable length long + * + * @throws java.lang.IndexOutOfBoundsException if the buffer runs out of bytes + */ + public long readVarLong(); + + /** + * Read a UTF-8 string from the byte buf, starting with the length of the string, with the data following comprising the data of the string + *

+ * Increments position by the number of bytes read + *

+ * + * @return a UTF-8 string + */ + public String readString(); + + /** + * Set the order of all the bytes in the buffer + * + * @param order the new order of all the bytes in the buffer + * + * @throws java.lang.UnsupportedOperationException if not supported + */ + public void setOrder(ByteOrder order); + + /** + * Create a new byte buffer with the same backing bytes, but independent position + * + * @return a new byte buffer with the same backing bytes, but independent position + */ + public ByteBuf duplicate(); + + /** + * Return the number of bytes currently in the buffer + * + * @return the number of bytes currently in the buffer + */ + public long readableBytes(); + +} diff --git a/src/main/java/net/md_5/jbeat/util/ByteBufs.java b/src/main/java/net/md_5/jbeat/util/ByteBufs.java new file mode 100755 index 0000000..c267b35 --- /dev/null +++ b/src/main/java/net/md_5/jbeat/util/ByteBufs.java @@ -0,0 +1,81 @@ +/** + * The MIT License + * Copyright (c) 2015 Techcable + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package net.md_5.jbeat.util; + +import lombok.*; + +import java.nio.ByteBuffer; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ByteBufs { + + /** + * Create a new resizable byte buffer with the specified initial bytes + * + * @return a new resizable byte buffer with the specified inital bytes + */ + public static ByteBuf create(byte[] bytes) { + ByteBuf buf = create(bytes.length); + buf.write(bytes, 0, bytes.length); + return buf; + } + + /** + * Create a new resizable byte buffer + * + * @return a new resizable byte buffer + */ + public static ByteBuf create() { + return new ResizableByteBuf(); + } + + protected static long readVarLong(ByteBuf buf) { + long data = 0, shift = 1; + while (true) { + byte x = buf.read(); + data += (x & 0x7F) * shift; + if ((x & 0x80) != 0x00) { + break; + } + shift <<= 7; + data += shift; + } + return data; + } + + /** + * Create a new resizable byte buffer with the specified initial capacity + * + * @param initialCapacity the initial capacity of the new byte buf + * + * @return a new resizable byte buffer + */ + public static ByteBuf create(int initialCapacity) { + return new ResizableByteBuf(initialCapacity); + } + + public static ByteBuf wrap(ByteBuffer backing) { + return new NIOByteBuf(backing); + } + +} diff --git a/src/main/java/net/md_5/jbeat/util/ByteList.java b/src/main/java/net/md_5/jbeat/util/ByteList.java new file mode 100755 index 0000000..c5dc38d --- /dev/null +++ b/src/main/java/net/md_5/jbeat/util/ByteList.java @@ -0,0 +1,38 @@ +/** + * The MIT License + * Copyright (c) 2015 Techcable + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package net.md_5.jbeat.util; + +public interface ByteList { + + public void add(byte b); + + public byte get(int index); + + public void addAll(byte[] bytes, int start, int end); + + public void addAll(byte[] bytes); + + public byte[] toArray(); + + public int size(); +} diff --git a/src/main/java/net/md_5/jbeat/util/ByteLists.java b/src/main/java/net/md_5/jbeat/util/ByteLists.java new file mode 100755 index 0000000..d62e9ec --- /dev/null +++ b/src/main/java/net/md_5/jbeat/util/ByteLists.java @@ -0,0 +1,65 @@ +/** + * The MIT License + * Copyright (c) 2015 Techcable + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package net.md_5.jbeat.util; + +import lombok.*; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ByteLists { + + public static ByteList synchronizedByteList(ByteList original) { + if (original instanceof SynchronizedByteList) return original; + return new SynchronizedByteList(original); + } + + @RequiredArgsConstructor + private static class SynchronizedByteList implements ByteList { + + private final ByteList backing; + + public synchronized void add(byte b) { + backing.add(b); + } + + public synchronized byte get(int index) { + return backing.get(index); + } + + public synchronized void addAll(byte[] bytes, int start, int end) { + backing.addAll(bytes, start, end); + } + + public synchronized void addAll(byte[] bytes) { + backing.addAll(bytes); + } + + public synchronized byte[] toArray() { + return backing.toArray(); + } + + public synchronized int size() { + return backing.size(); + } + } + +} diff --git a/src/main/java/net/md_5/jbeat/util/NIOByteBuf.java b/src/main/java/net/md_5/jbeat/util/NIOByteBuf.java new file mode 100755 index 0000000..0158eb7 --- /dev/null +++ b/src/main/java/net/md_5/jbeat/util/NIOByteBuf.java @@ -0,0 +1,132 @@ +/** + * The MIT License + * Copyright (c) 2015 Techcable + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package net.md_5.jbeat.util; + +import lombok.*; + +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +@RequiredArgsConstructor +class NIOByteBuf implements ByteBuf { + + private final ByteBuffer backing; + + public byte[] array() { + return backing.array(); + } + + public byte read() { + try { + return backing.get(); + } catch (BufferUnderflowException ex) { + throw new IndexOutOfBoundsException("Current position past buffer limit"); + } + } + + public int readInt() { + try { + return backing.getInt(); + } catch (BufferUnderflowException ex) { + throw new IndexOutOfBoundsException("Current position past buffer limit"); + } + } + + public byte read(int index) { + return backing.get(index); // Throws an IndexOutOfBoundsException on index out of bounds + } + + public void read(byte[] bytes) { + try { + backing.get(bytes); + } catch (BufferUnderflowException ex) { + throw new IndexOutOfBoundsException("Not enough bytes left to fill byte array"); + } + } + + public void write(byte[] bytes, int offset, int length) { + try { + backing.put(bytes, offset, length); + } catch (IndexOutOfBoundsException ex) { + throw new IllegalArgumentException(ex); + } catch (BufferOverflowException e) { + throw new IndexOutOfBoundsException("Not enough bytes in the buffer"); + } + } + + public void write(byte b) { + try { + backing.put(b); + } catch (BufferOverflowException e) { + throw new IndexOutOfBoundsException("Current position past buffer limit"); + } + } + + public void writeInt(int i) { + try { + backing.putInt(i); + } catch (BufferOverflowException e) { + throw new IndexOutOfBoundsException("Current position past buffer limit"); + } + } + + public long getPosition() { + return backing.position(); + } + + public void reset() { + backing.rewind(); + } + + public long readVarLong() { + return ByteBufs.readVarLong(this); + } + + public String readString() { + int length = (int) readVarLong(); + String ret = null; + if (length != 0) { + int limit = backing.limit(); + backing.limit(backing.position() + length); + ret = StandardCharsets.UTF_8.decode(backing).toString(); + backing.limit(limit); + } + return ret; + } + + public void setOrder(ByteOrder order) { + backing.order(order); + } + + public ByteBuf duplicate() { + ByteBuffer duplicated = backing.duplicate(); + return ByteBufs.wrap(duplicated); + } + + public long readableBytes() { + return backing.capacity(); + } +} diff --git a/src/main/java/net/md_5/jbeat/util/ResizableByteBuf.java b/src/main/java/net/md_5/jbeat/util/ResizableByteBuf.java new file mode 100755 index 0000000..cbf2ffc --- /dev/null +++ b/src/main/java/net/md_5/jbeat/util/ResizableByteBuf.java @@ -0,0 +1,119 @@ +/** + * The MIT License + * Copyright (c) 2015 Techcable + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package net.md_5.jbeat.util; + +import lombok.*; + +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +class ResizableByteBuf implements ByteBuf { + + private final ByteList byteList; + private int position; + + public ResizableByteBuf(int initialCapacity) { + this(new ByteArrayList(initialCapacity)); + } + + public ResizableByteBuf() { + this(new ByteArrayList()); + } + + public byte[] array() { + return byteList.toArray(); + } + + public byte read() { + byte read = byteList.get(position); + position++; + return read; + } + + public int readInt() { + byte b = read(); + byte b1 = read(); + byte b2 = read(); + byte b3 = read(); + return (((b) << 24) | ((b1 & 0xff) << 16) | ((b1 & 0xff) << 8) | ((b3 & 0xff))); + } + + public byte read(int index) { + return byteList.get(position); + } + + public void read(byte[] bytes) { + for (int i = 0; i < bytes.length; i++) { + bytes[i] = read(); + } + } + + public void write(byte[] bytes, int offset, int length) { + if (offset < 0 || bytes.length > offset || length < 0 || bytes.length - offset > length) throw new IllegalArgumentException(); + for (int i = offset; i < offset + length; i++) { + write(bytes[i]); + } + } + + public void write(byte b) { + byteList.add(b); + } + + public void writeInt(int i) { + write((byte) (i >> 24)); + write((byte) (i >> 16)); + write((byte) (i >> 8)); + write((byte) i); + } + + public long getPosition() { + return position; + } + + public void reset() { + position = 0; + } + + public long readVarLong() { + return ByteBufs.readVarLong(this); + } + + public String readString() { + int length = (int) readVarLong(); + byte[] bytes = new byte[length]; + return new String(bytes, StandardCharsets.UTF_8); + } + + public void setOrder(ByteOrder order) { + throw new UnsupportedOperationException(); + } + + public ByteBuf duplicate() { + return new ResizableByteBuf(byteList); + } + + public long readableBytes() { + return byteList.size(); + } +}