1 /* 2 * Copyright (c) 2017-2020 sel-project 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in all 12 * copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 * 22 */ 23 /** 24 * Copyright: Copyright (c) 2017-2020 sel-project 25 * License: MIT 26 * Authors: Kripth 27 * Source: $(HTTP github.com/sel-project/sel-nbt/sel/nbt/file.d, sel/nbt/file.d) 28 */ 29 module sel.nbt.file; 30 31 import std.conv : to; 32 33 static import std.bitmanip; 34 static import std.file; 35 import std.system : Endian; 36 import std.traits : isAbstractClass; 37 import std.typetuple : TypeTuple; 38 import std.zlib; 39 40 import xbuffer.buffer : Buffer; 41 42 import sel.nbt.stream; 43 import sel.nbt.tags; 44 45 enum Compression { 46 47 deflate, 48 gzip, 49 none 50 51 } 52 53 /** 54 * Example: 55 * --- 56 * alias Level = Format!(Named!Compound, ClassicStream!(Endian.bigEndian), Compression.gzip); 57 * auto level = Level.read("level.dat"); 58 * level.get!Compound("data")["LevelName"] = "Edited from code!"; 59 * Level.write(level, "level.dat"); 60 * --- 61 */ 62 class Format(T : Tag, S : Stream, Compression c, int level=6) if(!isAbstractClass!T && !isAbstractClass!S) { 63 64 private static S stream; 65 66 public static this() { 67 stream = new S(new Buffer(1024)); 68 } 69 70 public string location; 71 public T tag; 72 73 public this(string location, T tag=null) { 74 this.location = location; 75 this.tag = tag; 76 } 77 78 public this(T tag, string location="") { 79 this(location, tag); 80 } 81 82 public T load() { 83 84 stream.buffer.data = std.file.read(this.location); 85 86 this.loadHeader(stream.buffer); 87 88 static if(c != Compression.none) { 89 UnCompress uc = new UnCompress(cast(HeaderFormat)c); 90 void[] data = stream.buffer.data!void.dup; 91 stream.buffer.reset(); 92 stream.buffer.write(uc.uncompress(data)); 93 stream.buffer.write(uc.flush()); 94 } 95 96 //TODO nameless tag 97 this.tag = cast(T)stream.readTag(); 98 99 return this.tag; 100 101 } 102 103 protected void loadHeader(Buffer buffer) {} 104 105 public T save() { 106 107 stream.buffer.reset(); 108 //TODO nameless tag 109 stream.writeTag(this.tag); 110 111 static if(c != Compression.none) { 112 Compress compress = new Compress(level, cast(HeaderFormat)c); 113 stream.buffer.data = compress.compress(stream.buffer.data!void); 114 stream.buffer.write(compress.flush()); 115 } 116 117 this.saveHeader(stream.buffer); 118 119 std.file.write(this.location, stream.buffer.data!void); 120 121 return this.tag; 122 123 } 124 125 protected void saveHeader(Buffer buffer) {} 126 127 alias tag this; 128 129 } 130 131 alias JavaLevelFormat = Format!(Compound, ClassicStream!(Endian.bigEndian), Compression.gzip); 132 133 class PocketLevelFormat : Format!(Compound, ClassicStream!(Endian.littleEndian), Compression.none) { 134 135 private uint v = 5; 136 137 public this(string location, Compound tag=null) { 138 super(location, tag); 139 } 140 141 public this(Compound tag, string location="") { 142 super(tag, location); 143 } 144 145 protected override void loadHeader(Buffer buffer) { 146 this.v = buffer.read!(Endian.littleEndian, uint)(); 147 buffer.read!(Endian.littleEndian, uint)(); // size 148 } 149 150 protected override void saveHeader(Buffer buffer) { 151 void[] data = buffer.data!void.dup; 152 buffer.reset(); 153 buffer.write!(Endian.littleEndian)(this.v); 154 buffer.write!(Endian.littleEndian)(data.length.to!uint); 155 buffer.write(data); 156 } 157 158 alias tag this; 159 160 } 161 162 unittest { 163 164 auto java = new JavaLevelFormat("test/java.dat"); 165 java.load(); 166 auto data = java.get!Compound("Data", null); 167 assert(data !is null); 168 assert(data.has!String("LevelName") && data.get!String("LevelName", null) == "New World"); 169 assert(data.has!Int("version") && data.get!Int("version", null) == 19133); 170 171 java = new JavaLevelFormat(new Compound(new Named!Int("Test", 42)), "test.dat"); 172 java.save(); 173 auto u = new UnCompress(HeaderFormat.gzip); 174 auto b = cast(ubyte[])u.uncompress(std.file.read("test.dat")); 175 b ~= cast(ubyte[])u.flush(); 176 assert(b == [10, 0, 0, 3, 0, 4, 84, 101, 115, 116, 0, 0, 0, 42, 0]); 177 178 auto pocket = new PocketLevelFormat("test/pocket.dat"); 179 pocket.load(); 180 assert(pocket.has!String("LevelName") && pocket.get!String("LevelName", null) == "AAAAAAAAAA"); 181 assert(pocket.has!Int("Difficulty") && pocket.get!Int("Difficulty", null) == 2); 182 183 pocket = new PocketLevelFormat(new Compound(new Named!Int("Test", 42)), "test.dat"); 184 pocket.save(); 185 assert(cast(ubyte[])std.file.read("test.dat") == [5, 0, 0, 0, 15, 0, 0, 0, 10, 0, 0, 3, 4, 0, 84, 101, 115, 116, 42, 0, 0, 0, 0]); 186 187 }