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