1 module ppc;
2 public import ppc.image;
3 public import ppc.bundle;
4 public import ppc.shader;
5 
6 public import ppc.exceptions;
7 import ppc.utils;
8 
9 import std.file;
10 import std.stdio;
11 import std.conv;
12 import std.bitmanip;
13 
14 /**
15 	PPCoHead Header Name
16 */
17 public static ubyte[] ContentHeader() {
18 	return [0x50, 0x50, 0x43, 0x6f, 0x48, 0x65, 0x61, 0x64];
19 }
20 
21 public enum FileTypeId : ubyte {
22 	Content,
23 	Bundle,
24 	PlainText
25 }
26 
27 public enum TypeId : ubyte {
28 	Texture2D = 0,
29 	Bundle = 1,
30 	Script = 2,
31 	TextureList = 3,
32 	Model = 4,
33 	Mesh = 5,
34 	Audio = 6,
35 	Sample = 7,
36 	Shader = 8,
37 	Dictionary = 9,
38 	Data = 10,
39 	Raw = 11
40 }
41 
42 public enum ContentLicense : ubyte {
43 	Propriatary,
44 	MIT,
45 	GPL2,
46 	LGPL2,
47 	GPL3,
48 	LGPL3,
49 	CC,
50 	CC_BY_A,
51 	//TODO: Add more license types.
52 }
53 
54 public abstract class ContentFactory {
55 	private ubyte id;
56 
57 	/**
58 		Constructs a new factory.
59 	*/
60 	public this(ubyte id) {
61 		this.id = id;
62 	}
63 
64 	/**
65 		Construct constructs a new content type.
66 	*/
67 	public abstract Content Construct(ubyte[] data);
68 
69 	/**
70 		The ID representation of the content.
71 	*/
72 	public @property ubyte Id() { return this.id; }
73 }
74 
75 /**
76 	Content is a basic content construct.
77 */
78 public class Content {
79 	/**
80 		<Protected> data is the byte data the content is made out of.
81 	*/
82 	protected ubyte[] data;
83 		
84 	/**
85 		The type id of the content file.
86 	*/
87 	public ubyte Type;
88 
89 	/**
90 		The name of the content.
91 	*/
92 	public string Name;
93 
94 	/**
95 		ubyte representation of the type.
96 	*/
97 	public TypeId TypeID() { return cast(TypeId) Type; }
98 
99 	/**
100 		Constructs content type from data.
101 	*/
102 	public this(TypeId type) {
103 		this.Type = type;
104 		this.data = [];
105 	}
106 
107 	/**
108 		Constructs content type from data.
109 	*/
110 	public this(TypeId type, string name) {
111 		this.Type = type;
112 		this.Name = name;
113 		this.data = [];
114 	}
115 
116 	/**
117 		Constructs content type from data.
118 	*/
119 	public this(ubyte[] data) {
120 		this.ConvertFull(data);
121 	}
122 
123 	/**
124 		Converts input bytes into this type of content.
125 	*/
126 	protected abstract void Convert(ubyte[] data);
127 
128 	public void ConvertFull(ubyte[] data) {
129 		this.Type = data[0];
130 		int name_len = bigEndianToNative!int(data[1..5]);
131 		this.Name = cast(string)data[5..5+name_len];
132 
133 		ubyte[] d = data[5+name_len..$];
134 		this.Convert(d);
135 	}
136 
137 	/**
138 		Returns an ubyte array of the compiled representation of the content.
139 	*/
140 	protected abstract ubyte[] Compile();
141 
142 	public ubyte[] CompileFull() {
143 		ubyte[] data = [this.Type];
144 		ubyte[] n = cast(ubyte[])this.Name;
145 		ubyte[int.sizeof] n_len = nativeToBigEndian(cast(int)n.length);
146 
147 		data = Combine(data, n_len);
148 		data = Combine(data, n);
149 		return Combine(data, Compile());
150 	}
151 }
152 
153 public class ContentInfo {
154 
155 	/**
156 		The author.
157 	*/
158 	public string Author;
159 
160 	/**
161 		The License.
162 	*/
163 	public ContentLicense License;
164 
165 	/**
166 		A message left by its creator, usually for license text.
167 		But can be used to hide angry messages to people decompiling the content.
168 	*/
169 	public string Message;
170 
171 	/**
172 		Binary representation of the content info header portion.
173 	*/
174 	public @property ubyte[] Bytes() {
175 
176 		ubyte[] author = cast(ubyte[])Author;
177 		ubyte[4] author_len = nativeToBigEndian(cast(int)author.length);
178 
179 		ubyte[] msg = cast(ubyte[])Message;
180 		ubyte[4] msg_len = nativeToBigEndian(cast(int)msg.length);
181 		
182 		return author_len ~ author ~ msg_len ~ msg ~ [cast(ubyte)License];
183 	}
184 
185 	public this() {
186 		this.Author = "Nobody";
187 		this.License = ContentLicense.Propriatary;
188 		this.Message = "<Insert Message Here>";
189 	}
190 
191 	public static ContentInfo FromBytes(ubyte[] data) {
192 		ContentInfo inf = new ContentInfo();
193 		//Author Length
194 		int author_len = bigEndianToNative!int(data[0..4]);
195 
196 		//Message Length
197 		ubyte[4] msg_ba = data[4+author_len..4+author_len+4];
198 		int msg_len = bigEndianToNative!int(msg_ba);
199 		
200 		//Set Author, Message and License.
201 		inf.Author = cast(string)data[4..4+author_len];
202 		inf.Message = cast(string)data[4+author_len+4..4+author_len+4+msg_len];
203 		inf.License = cast(ContentLicense)data[$-1];
204 		return inf;
205 	}
206 }
207 
208 /**
209 	ContentFile is a content file.
210 */
211 public class ContentFile {
212 	/**
213 		The data used to construct a higher level content type.
214 	*/
215 	public Content Data;
216 
217 	/**
218 		Header information for the content file.
219 	*/
220 	public ContentInfo Info;
221 	
222 	/**
223 		The type id of the content file.
224 	*/
225 	public ubyte Type;
226 
227 	/**
228 		ubyte representation of the type.
229 	*/
230 	public FileTypeId TypeID() { return cast(FileTypeId) Type; }
231 
232 	this(FileTypeId type) {
233 		this.Type = type;
234 	}
235 
236 	/**
237 		Construct a raw content class.
238 	*/
239 	this(ubyte id, ubyte[] data) {
240 		this.Type = id;
241 		this.Data = from_file_data(data);
242 	}
243 
244 	/**
245 		Construct a raw content class.
246 	*/
247 	this(FileTypeId id, ubyte[] data) {
248 		this.Type = cast(ubyte)id;
249 		this.Data = from_file_data(data);
250 	}
251 
252 	/**
253 		Saves the content to disk.
254 	*/
255 	public void Save(string name) {
256 		File f = File(name, "w+");
257 		f.rawWrite(Compile());
258 		f.close();
259 	}
260 
261 	/**
262 		Turn the Content class into a file-writable byte array.
263 	*/
264 	public ubyte[] Compile() {
265 		// Info
266 		ubyte[] inf = this.Info.Bytes;
267 		ubyte[8] infl = nativeToBigEndian(inf.length);
268 
269 		// Data
270 		ubyte[] dat = this.Data.CompileFull();
271 
272 		return ContentHeader ~ [this.Type] ~ infl ~ inf ~ dat;
273 	}
274 
275 	public static ContentFile ReadContentFile(ubyte[] data) {
276 		// TODO: Make a check for the content headers presence (PPCoHead)
277 
278 		ContentFile cf = new ContentFile(cast(FileTypeId)data[ContentHeader.length]);
279 
280 		// Data
281 		ubyte[] dat = data[ContentHeader.length+1..data.length];
282 
283 		// Info Length
284 		ulong infl = bigEndianToNative!ulong(dat[0..8]);
285 
286 		// Info
287 		cf.Info = ContentInfo.FromBytes(dat[8..8+infl]);
288 
289 		//Data
290 		cf.Data = from_file_data(dat[8+infl..dat.length]);
291 		return cf;
292 	}
293 }
294 
295 /**
296 	Adds a type to the type factory.
297 */
298 public void AddFactory(ContentFactory fac) {
299 	factories[fac.Id.text] = fac;
300 }
301 
302 private ContentFactory[string] factories;
303 private bool factories_setup = false;
304 
305 /**
306 	Imports all of the factories needed to build types automagically.
307 */
308 public void SetupBaseFactories() {
309 	factories_setup = true;
310 	AddFactory(new RawContentFactory());
311 	AddFactory(new ImageFactory());
312 	AddFactory(new BundleFactory());
313 	AddFactory(new ShaderFactory());
314 }
315 
316 private Content from_file_data(ubyte[] data) {
317 	if (!factories_setup) throw new Exception("Base factories has not been set up, please run SetupBaseFactories();");
318 	if(factories[data[0].text] is null) throw new Exception("No content factory to handle type id " ~ data[0].text);
319 	return factories[data[0].text].Construct(data);
320 }
321 
322 public class RawContentFactory : ContentFactory {
323 	public this() {
324 		super(TypeId.Raw);
325 	}
326 
327 	public override Content Construct(ubyte[] data) {
328 		return new RawContent(data);
329 	}
330 }
331 
332 public class RawContent : Content {
333 	public this(string name) {
334 		super(TypeId.Raw, name);
335 	}
336 
337 	public this(ubyte[] b) {
338 		super(b);
339 	}
340 
341 	public override void Convert(ubyte[] data) {
342 		this.data = data;
343 	}
344 
345 	public override ubyte[] Compile() {
346 		return this.data;
347 	}
348 	
349 }
350 
351 public class ContentManager {
352 
353 	/**
354 		Load loads a content file (from memory)
355 	*/
356 	public static Content Load(ubyte[] data) {
357 		return from_file_data(data);
358 	}
359 
360 	/**
361 		Load loads a content file.
362 	*/
363 	public static Content Load(string file) {
364 		File f = File(file);
365 		ubyte[] data;
366 		foreach(ubyte[] buff; f.byChunk(4096)) {
367 			data = Combine(data, buff);
368 		}
369 
370 		ContentFile fl = ContentFile.ReadContentFile(data);
371 		f.close();
372 		return fl.Data;
373 	}
374 	/*
375 	public static Content LoadRaw(string file) {
376 		File f = File(file);
377 		ubyte[ContentHeader.length] header;
378 		ubyte[1] tyid;
379 		ubyte[] data;
380 
381 		f.byChunk(header);
382 		if (header != ContentHeader) throw new InvalidFileFormatException();
383 		f.byChunk(tyid);
384 		foreach(ubyte[] buff; f.byChunk(4096)) {
385 			data = Combine(data, buff);
386 		}
387 		f.close();
388 		return new Content(cast(TypeId)tyid[0], data);
389 	}
390 
391 	public static Image LoadImage(string file) {
392 		return new Image(LoadRaw(file));
393 	}*/
394 }
395 /*
396 public class ContentConverter {
397 	public static void ConvertImage(string input, string output) {
398 		Image img = ConvertImage(input);
399 		img.Compile();
400 	}
401 	
402 	public static Image ConvertImage(string input) {
403 		File fr = File(input);
404 		ubyte[] frd;
405 		foreach(ubyte[] chunk; fr.byChunk(4096)) {
406 			frd = Combine(frd, chunk);
407 		}
408 		fr.close();
409 		Content t = new Content(TypeId.Texture2D, frd);
410 		Image img = new Image(t);
411 		return img;
412 	}
413 
414 	public static void ConvertToFile(string input, string output) {
415 		if (is_ext(input, "png") || is_ext(input, "jpg") || is_ext(input, "tga")) {
416 			ConvertImage(input, output);
417 			return;
418 		}
419 		throw new Exception("Unsupported file format");
420 	}
421 
422 	private static bool is_ext(string input, string ext) {
423 		return input[input.length-3..input.length] == ext;
424 	}
425 }*/