1 /*
2  * Copyright (c) 2017-2019 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-2019 sel-project
25  * License: MIT
26  * Authors: Kripth
27  * Source: $(HTTP github.com/sel-project/selery/source/selery/util/resourcepack.d, selery/util/resourcepack.d)
28  */
29 module selery.util.resourcepack;
30 
31 import std.algorithm : canFind;
32 import std.concurrency : Tid, send;
33 import std.conv : to;
34 import std.file : read, dirEntries, SpanMode, isFile;
35 import std.json : JSONValue;
36 import std.random : uniform;
37 import std.regex : ctRegex, replaceAll;
38 import std.socket;
39 import std..string : replace, join, indexOf, startsWith;
40 import std.uuid : UUID;
41 import std.zip;
42 
43 import sel.net.http : StatusCodes, Response;
44 
45 import selery.about : Software;
46 import selery.node.server : NodeServer;
47 import selery.util.tuple : Tuple;
48 
49 auto createResourcePacks(shared NodeServer server, UUID uuid, string[] textures) {
50 
51 	auto java2 = new ZipArchive();
52 	auto java3 = new ZipArchive();
53 	auto pocket = new ZipArchive();
54 
55 	void add(ArchiveMember member) {
56 		java2.addMember(member);
57 		java3.addMember(member);
58 	}
59 
60 	// add sel's modified textures to minecraft and pocket resource packs
61 	foreach(t ; textures) {
62 		foreach(string file ; dirEntries(t, SpanMode.breadth)) {
63 			if(isFile(file)) {
64 				immutable name = replace(file[t.length..$], "\\", "/");
65 				auto data = cast(ubyte[])read(file);
66 				add(create("assets/minecraft/textures/" ~ name, data));
67 				pocket.addMember(create("textures/" ~ name, data));
68 			}
69 		}
70 	}
71 
72 	// add icon
73 	auto icon = cast(ubyte[])server.config.files.readAsset("icon.png");
74 	add(create("pack.png", icon));
75 	pocket.addMember(create("pack_icon.png", icon));
76 
77 	// create minecraft's manifest
78 	auto description = JSONValue("The default look of Selery");
79 	java2.addMember(create("pack.mcmeta", cast(ubyte[])JSONValue(["pack": JSONValue(["pack_format": JSONValue(2), "description": description])]).toString()));
80 	java3.addMember(create("pack.mcmeta", cast(ubyte[])JSONValue(["pack": JSONValue(["pack_format": JSONValue(3), "description": description])]).toString()));
81 
82 	// create pocket's manifest
83 	auto vers = JSONValue(cast(ubyte[])Software.versions);
84 	pocket.addMember(create("manifest.json", cast(ubyte[])JSONValue(["format_version": JSONValue(1), "header": JSONValue(["description": description, "name": JSONValue("SEL"), "uuid": JSONValue(uuid.toString()), "version": vers]), "modules": JSONValue([JSONValue(["description": description, "type": JSONValue("resources"), "uuid": JSONValue(server.nextUUID.toString()), "version": vers])])]).toString()));
85 
86 	return Tuple!(void[], "java2", void[], "java3", void[], "pocket1")(java2.build(), java3.build(), pocket.build());
87 
88 }
89 
90 private ArchiveMember create(string name, ubyte[] data) {
91 	auto ret = new ArchiveMember();
92 	ret.name = name;
93 	ret.expandedData(data);
94 	ret.compressionMethod = CompressionMethod.deflate;
95 	return ret;
96 }
97 
98 void serveResourcePacks(Tid server, string pack2, string pack3) {
99 	
100 	auto port = uniform(ushort(999), ushort.max);
101 	
102 	auto socket = new TcpSocket();
103 	socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true);
104 	socket.bind(new InternetAddress("0.0.0.0", port));
105 	socket.listen(16);
106 	socket.blocking = true;
107 	
108 	send(server, port);
109 	
110 	char[] buffer = new char[6];
111 	
112 	const(void)[] response2 = Response(StatusCodes.ok, ["Content-Type": "application/zip", "Server": Software.display], pack2).toString();
113 	const(void)[] response3 = Response(StatusCodes.ok, ["Content-Type": "application/zip", "Server": Software.display], pack3).toString();
114 	
115 	while(true) {
116 		
117 		//TODO create a non-blocking handler
118 		
119 		auto client = socket.accept();
120 		
121 		auto r = client.receive(buffer);
122 		if(r == 6) { // length of the buffer
123 			if(buffer[0..5] == "GET /") {
124 				if(buffer[5] == '2') client.send(response2);
125 				else if(buffer[5] == '3') client.send(response3);
126 				else client.close();
127 			}
128 		}
129 		
130 	}
131 	
132 }