1 /* 2 * Copyright (c) 2017 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 * See the GNU Lesser General Public License for more details. 13 * 14 */ 15 module sel.nbt.file; 16 17 // test 18 import std.conv; 19 import std.stdio : writeln; 20 21 static import std.bitmanip; 22 static import std.file; 23 import std.system : Endian; 24 import std.traits : isAbstractClass; 25 import std.typetuple : TypeTuple; 26 import std.zlib; 27 28 import sel.nbt.stream; 29 import sel.nbt.tags; 30 31 enum Compression { 32 33 deflate, 34 gzip, 35 none 36 37 } 38 39 /** 40 * Example: 41 * --- 42 * alias Level = Format!(Named!Compound, ClassicStream!(Endian.bigEndian), Compression.gzip); 43 * auto level = Level.read("level.dat"); 44 * level.get!Compound("data")["LevelName"] = "Edited from code!"; 45 * Level.write(level, "level.dat"); 46 * --- 47 */ 48 class Format(T : Tag, S : Stream, Compression c, int level=6) if(!isAbstractClass!T && !isAbstractClass!S) { 49 50 private static S stream; 51 52 public static this() { 53 stream = new S(); 54 } 55 56 public string location; 57 public T tag; 58 59 public this(string location, T tag=null) { 60 this.location = location; 61 this.tag = tag; 62 } 63 64 public this(T tag, string location="") { 65 this(location, tag); 66 } 67 68 public T load() { 69 70 ubyte[] data = cast(ubyte[])std.file.read(this.location); 71 72 this.loadHeader(data); 73 74 static if(c != Compression.none) { 75 UnCompress uc = new UnCompress(cast(HeaderFormat)c); 76 data = cast(ubyte[])uc.uncompress(data); 77 data ~= cast(ubyte[])uc.flush(); 78 } 79 80 stream.buffer = data; 81 82 //TODO nameless tag 83 this.tag = cast(T)stream.readTag(); 84 85 return this.tag; 86 87 } 88 89 protected void loadHeader(ref ubyte[] data) {} 90 91 public T save() { 92 93 stream.buffer.length = 0; 94 //TODO nameless tag 95 stream.writeTag(this.tag); 96 ubyte[] data = stream.buffer; 97 98 static if(c != Compression.none) { 99 Compress compress = new Compress(level, cast(HeaderFormat)c); 100 data = cast(ubyte[])compress.compress(data); 101 data ~= cast(ubyte[])compress.flush(); 102 } 103 104 this.saveHeader(data); 105 106 std.file.write(this.location, data); 107 108 return this.tag; 109 110 } 111 112 protected void saveHeader(ref ubyte[] data) {} 113 114 alias tag this; 115 116 } 117 118 alias MinecraftLevelFormat = Format!(Compound, ClassicStream!(Endian.bigEndian), Compression.gzip); 119 120 class PocketLevelFormat : Format!(Compound, ClassicStream!(Endian.littleEndian), Compression.none) { 121 122 private uint v = 5; 123 124 public this(string location, Compound tag=null) { 125 super(location, tag); 126 } 127 128 public this(Compound tag, string location="") { 129 super(tag, location); 130 } 131 132 protected override void loadHeader(ref ubyte[] data) { 133 if(data.length >= 8) { 134 this.v = std.bitmanip.read!(uint, Endian.littleEndian)(data); 135 size_t size = std.bitmanip.read!(uint, Endian.littleEndian)(data); 136 assert(data.length == size); 137 } 138 } 139 140 protected override void saveHeader(ref ubyte[] data) { 141 data = std.bitmanip.nativeToLittleEndian(this.v).dup ~ std.bitmanip.nativeToLittleEndian(cast(uint)data.length).dup ~ data; 142 } 143 144 alias tag this; 145 146 } 147 148 unittest { 149 150 auto minecraft = new MinecraftLevelFormat("test/minecraft.dat"); 151 minecraft.load(); 152 auto data = minecraft.get!Compound("Data", null); 153 assert(data !is null); 154 assert(data.has!String("LevelName") && data.get!String("LevelName", null) == "New World"); 155 assert(data.has!Int("version") && data.get!Int("version", null) == 19133); 156 157 minecraft = new MinecraftLevelFormat(new Compound(new Named!Int("Test", 42)), "test.dat"); 158 minecraft.save(); 159 auto u = new UnCompress(HeaderFormat.gzip); 160 auto b = cast(ubyte[])u.uncompress(std.file.read("test.dat")); 161 b ~= cast(ubyte[])u.flush(); 162 assert(b == [10, 0, 0, 3, 0, 4, 84, 101, 115, 116, 0, 0, 0, 42, 0]); 163 164 auto pocket = new PocketLevelFormat("test/pocket.dat"); 165 pocket.load(); 166 assert(pocket.has!String("LevelName") && pocket.get!String("LevelName", null) == "AAAAAAAAAA"); 167 assert(pocket.has!Int("Difficulty") && pocket.get!Int("Difficulty", null) == 2); 168 169 pocket = new PocketLevelFormat(new Compound(new Named!Int("Test", 42)), "test.dat"); 170 pocket.save(); 171 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]); 172 173 }