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