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 }*/