├── GameFormatReader.sln
├── GameFormatReader
├── Common
│ ├── Endian.cs
│ ├── EndianBinaryReader.cs
│ ├── EndianBinaryWriter.cs
│ └── EndianUtils.cs
├── GCWii
│ ├── Archive
│ │ ├── RARC.cs
│ │ └── U8.cs
│ ├── Binaries
│ │ └── DOL.cs
│ ├── Compression
│ │ └── Yaz0.cs
│ ├── Discs
│ │ ├── Disc.cs
│ │ ├── DiscHeader.cs
│ │ ├── DiscType.cs
│ │ ├── GC
│ │ │ ├── Apploader.cs
│ │ │ ├── DiscGC.cs
│ │ │ ├── DiscHeaderGC.cs
│ │ │ ├── FST.cs
│ │ │ └── FSTEntry.cs
│ │ ├── Region.cs
│ │ └── Wii
│ │ │ ├── DiscHeaderWii.cs
│ │ │ └── DiscWii.cs
│ ├── Graphics
│ │ ├── BNR.cs
│ │ ├── Enums
│ │ │ ├── TLUTFormat.cs
│ │ │ ├── TextureFilter.cs
│ │ │ ├── TextureFormat.cs
│ │ │ └── WrapMode.cs
│ │ └── TPL.cs
│ └── Sound
│ │ └── BRSTM.cs
├── GameFormatReader.csproj
└── Properties
│ └── AssemblyInfo.cs
└── Readme.md
/GameFormatReader.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.30723.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFormatReader", "GameFormatReader\GameFormatReader.csproj", "{AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/GameFormatReader/Common/Endian.cs:
--------------------------------------------------------------------------------
1 | namespace GameFormatReader.Common
2 | {
3 | ///
4 | /// Enum that defines endian representations.
5 | ///
6 | public enum Endian
7 | {
8 | /// Little endian
9 | Little,
10 | /// Big endian
11 | Big,
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/GameFormatReader/Common/EndianBinaryReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 |
6 | // TODO: ReadDecimal method equivalent.
7 | // How do you even go about flipping the endianness on this type?
8 |
9 | namespace GameFormatReader.Common
10 | {
11 | ///
12 | /// implementation that can read in different formats.
13 | ///
14 | public sealed class EndianBinaryReader : BinaryReader
15 | {
16 | #region Fields
17 |
18 | // Underlying endianness being used by the .NET Framework.
19 | private static readonly bool systemLittleEndian = BitConverter.IsLittleEndian;
20 |
21 | #endregion
22 |
23 | #region Properties
24 |
25 | ///
26 | /// Current this EndianBinaryReader is using.
27 | ///
28 | public Endian CurrentEndian
29 | {
30 | get;
31 | set;
32 | }
33 |
34 | #endregion
35 |
36 | #region Constructors
37 |
38 | ///
39 | /// Constructor. Uses UTF-8 by default for character encoding.
40 | ///
41 | /// The to wrap within this EndianBinaryReader.
42 | /// The to use when reading files..
43 | public EndianBinaryReader(Stream stream, Endian endian) : base(stream)
44 | {
45 | CurrentEndian = endian;
46 | }
47 |
48 | ///
49 | /// Constructor
50 | ///
51 | /// The to wrap within this EndianBinaryReader.
52 | /// The to use for characters.
53 | /// The to use when reading files.
54 | public EndianBinaryReader(Stream stream, Encoding encoding, Endian endian) : base(stream, encoding)
55 | {
56 | CurrentEndian = endian;
57 | }
58 |
59 | ///
60 | /// Constructor.
61 | ///
62 | /// The to wrap within this EndianBinaryReader.
63 | /// The to use for characters.
64 | /// Whether or not to leave the stream open after this EndianBinaryReader is disposed.
65 | /// The to use when reading from files.
66 | public EndianBinaryReader(Stream stream, Encoding encoding, bool leaveOpen, Endian endian) : base (stream, encoding, leaveOpen)
67 | {
68 | CurrentEndian = endian;
69 | }
70 |
71 | ///
72 | /// Constructor.
73 | ///
74 | /// Data to encapsulate
75 | /// to use when reading the data.
76 | public EndianBinaryReader(byte[] data, Endian endian)
77 | : base (new MemoryStream(data))
78 | {
79 | CurrentEndian = endian;
80 | }
81 |
82 | ///
83 | /// Constructor.
84 | ///
85 | /// Data to encapsulate
86 | /// The to use for characters.
87 | /// to use when reading the data.
88 | public EndianBinaryReader(byte[] data, Encoding encoding, Endian endian)
89 | : base(new MemoryStream(data), encoding)
90 | {
91 | CurrentEndian = endian;
92 | }
93 |
94 | ///
95 | /// Constructor.
96 | ///
97 | /// Data to encapsulate
98 | /// The to use for characters.
99 | /// Whether or not to leave the stream open after this EndianBinaryReader is disposed.
100 | /// to use when reading the data.
101 | public EndianBinaryReader(byte[] data, Encoding encoding, bool leaveOpen, Endian endian)
102 | : base(new MemoryStream(data), encoding, leaveOpen)
103 | {
104 | CurrentEndian = endian;
105 | }
106 |
107 | #endregion
108 |
109 | #region Public Methods
110 |
111 | #region Overrides
112 |
113 | public override short ReadInt16()
114 | {
115 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
116 | !systemLittleEndian && CurrentEndian == Endian.Big)
117 | {
118 | return base.ReadInt16();
119 | }
120 |
121 | // BE to LE or LE to BE
122 | return base.ReadInt16().SwapBytes();
123 | }
124 |
125 | public override ushort ReadUInt16()
126 | {
127 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
128 | !systemLittleEndian && CurrentEndian == Endian.Big)
129 | {
130 | return base.ReadUInt16();
131 | }
132 |
133 | // BE to LE or LE to BE
134 | return base.ReadUInt16().SwapBytes();
135 | }
136 |
137 | public override int ReadInt32()
138 | {
139 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
140 | !systemLittleEndian && CurrentEndian == Endian.Big)
141 | {
142 | return base.ReadInt32();
143 | }
144 |
145 | // BE to LE or LE to BE
146 | return base.ReadInt32().SwapBytes();
147 | }
148 |
149 | public override uint ReadUInt32()
150 | {
151 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
152 | !systemLittleEndian && CurrentEndian == Endian.Big)
153 | {
154 | return base.ReadUInt32();
155 | }
156 |
157 | // BE to LE or LE to BE
158 | return base.ReadUInt32().SwapBytes();
159 | }
160 |
161 | public override long ReadInt64()
162 | {
163 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
164 | !systemLittleEndian && CurrentEndian == Endian.Big)
165 | {
166 | return base.ReadInt64();
167 | }
168 |
169 | // BE to LE or LE to BE
170 | return base.ReadInt64().SwapBytes();
171 | }
172 |
173 | public override ulong ReadUInt64()
174 | {
175 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
176 | !systemLittleEndian && CurrentEndian == Endian.Big)
177 | {
178 | return base.ReadUInt64();
179 | }
180 |
181 | // BE to LE or LE to BE
182 | return base.ReadUInt64().SwapBytes();
183 | }
184 |
185 | public override float ReadSingle()
186 | {
187 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
188 | !systemLittleEndian && CurrentEndian == Endian.Big)
189 | {
190 | return base.ReadSingle();
191 | }
192 |
193 | // BE to LE or LE to BE
194 | float temp = base.ReadSingle();
195 |
196 | byte[] floatBytes = BitConverter.GetBytes(temp);
197 | Array.Reverse(floatBytes);
198 |
199 | return BitConverter.ToSingle(floatBytes, 0);
200 | }
201 |
202 | public override double ReadDouble()
203 | {
204 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
205 | !systemLittleEndian && CurrentEndian == Endian.Big)
206 | {
207 | return base.ReadDouble();
208 | }
209 |
210 | // BE to LE or LE to BE
211 | double temp = base.ReadDouble();
212 |
213 | byte[] doubleBytes = BitConverter.GetBytes(temp);
214 | Array.Reverse(doubleBytes);
215 |
216 | return BitConverter.ToDouble(doubleBytes, 0);
217 | }
218 |
219 | #endregion
220 |
221 | #region PeekRead[x] Methods
222 |
223 | ///
224 | /// Reads a signed byte relative to the current position
225 | /// in the underlying without advancing position.
226 | ///
227 | /// the signed byte that was read.
228 | public sbyte PeekReadSByte()
229 | {
230 | sbyte res = ReadSByte();
231 |
232 | BaseStream.Position -= sizeof(SByte);
233 |
234 | return res;
235 | }
236 |
237 | ///
238 | /// Reads an unsigned byte relative to the current position
239 | /// in the underlying without advancing position.
240 | ///
241 | /// the byte that was read.
242 | public byte PeekReadByte()
243 | {
244 | byte res = ReadByte();
245 |
246 | BaseStream.Position -= sizeof(Byte);
247 |
248 | return res;
249 | }
250 |
251 | ///
252 | /// Reads a signed 16-bit integer relative to the current position
253 | /// in the underlying without advancing position.
254 | ///
255 | /// the signed 16-bit integer that was read.
256 | public short PeekReadInt16()
257 | {
258 | short res = ReadInt16();
259 |
260 | BaseStream.Position -= sizeof(Int16);
261 |
262 | return res;
263 | }
264 |
265 | ///
266 | /// Reads an unsigned 16-bit integer relative to the current position
267 | /// in the underlying without advancing position.
268 | ///
269 | /// the unsigned 16-bit integer that was read.
270 | public ushort PeekReadUInt16()
271 | {
272 | ushort res = ReadUInt16();
273 |
274 | BaseStream.Position -= sizeof(UInt16);
275 |
276 | return res;
277 | }
278 |
279 | ///
280 | /// Reads a signed 32-bit integer relative to the current position
281 | /// in the underlying without advancing position.
282 | ///
283 | /// the signed 32-bit integer that was read.
284 | public int PeekReadInt32()
285 | {
286 | int res = ReadInt32();
287 |
288 | BaseStream.Position -= sizeof(Int32);
289 |
290 | return res;
291 | }
292 |
293 | ///
294 | /// Reads an unsigned 32-bit integer relative to the current position
295 | /// in the underlying without advancing position.
296 | ///
297 | /// the unsigned 32-bit integer that was read.
298 | public uint PeekReadUInt32()
299 | {
300 | uint res = ReadUInt32();
301 |
302 | BaseStream.Position -= sizeof(UInt32);
303 |
304 | return res;
305 | }
306 |
307 | ///
308 | /// Reads a signed 64-bit integer relative to the current position
309 | /// in the underlying without advancing position.
310 | ///
311 | /// the signed 64-bit integer that was read.
312 | public long PeekReadInt64()
313 | {
314 | long res = ReadInt64();
315 |
316 | BaseStream.Position -= sizeof(Int64);
317 |
318 | return res;
319 | }
320 |
321 | ///
322 | /// Reads an unsigned 64-bit integer relative to the current position
323 | /// in the underlying without advancing position.
324 | ///
325 | /// the unsigned 64-bit integer that was read.
326 | public ulong PeekReadUInt64()
327 | {
328 | ulong res = ReadUInt64();
329 |
330 | BaseStream.Position -= sizeof(UInt64);
331 |
332 | return res;
333 | }
334 |
335 | ///
336 | /// Reads a 32-bit floating-point number relative to the current position
337 | /// in the underlying without advancing position.
338 | ///
339 | /// the 32-bit floating-point number that was read.
340 | public float PeekReadSingle()
341 | {
342 | float res = ReadSingle();
343 |
344 | BaseStream.Position -= sizeof(Single);
345 |
346 | return res;
347 | }
348 |
349 | ///
350 | /// Reads a 64-bit floating-point number relative to the current position
351 | /// in the underlying without advancing position.
352 | ///
353 | /// the 64-bit floating-point number that was read.
354 | public double PeekReadDouble()
355 | {
356 | double res = ReadDouble();
357 |
358 | BaseStream.Position -= sizeof(Double);
359 |
360 | return res;
361 | }
362 |
363 | ///
364 | /// Reads count number of bytes into an array without
365 | /// advancing the position of the underlying .
366 | ///
367 | /// Number of bytes to read.
368 | /// byte array containing the bytes read.
369 | public byte[] PeekReadBytes(int count)
370 | {
371 | if (count < 0)
372 | throw new ArgumentException($"{nameof(count)} cannot be negative", nameof(count));
373 |
374 | byte[] res = ReadBytes(count);
375 |
376 | BaseStream.Position -= count;
377 |
378 | return res;
379 | }
380 |
381 | #endregion
382 |
383 | #region Read[x]At Methods
384 |
385 | ///
386 | /// Reads a boolean at a given offset without
387 | /// changing the underlying position.
388 | ///
389 | /// The offset to read at.
390 | /// the boolean value read at the offset.
391 | public bool ReadBooleanAt(long offset)
392 | {
393 | long origPos = BaseStream.Position;
394 |
395 | // Seek to the given offset
396 | BaseStream.Position = offset;
397 |
398 | // Read
399 | bool res = ReadBoolean();
400 |
401 | // Flip back to the original position.
402 | BaseStream.Position = origPos;
403 |
404 | return res;
405 | }
406 |
407 | ///
408 | /// Reads a signed byte at a given offset without
409 | /// changing the underlying position.
410 | ///
411 | /// The offset to read at.
412 | /// the signed byte read at the offset.
413 | public sbyte ReadSByteAt(long offset)
414 | {
415 | long origPos = BaseStream.Position;
416 |
417 | // Seek to the given offset
418 | BaseStream.Position = offset;
419 |
420 | // Read
421 | sbyte res = ReadSByte();
422 |
423 | // Flip back to the original position.
424 | BaseStream.Position = origPos;
425 |
426 | return res;
427 | }
428 |
429 | ///
430 | /// Reads an unsigned byte at a given offset without
431 | /// changing the underlying position.
432 | ///
433 | /// The offset to read at.
434 | /// the unsigned byte read at the offset.
435 | public byte ReadByteAt(long offset)
436 | {
437 | long origPos = BaseStream.Position;
438 |
439 | // Seek to the given offset
440 | BaseStream.Position = offset;
441 |
442 | // Read
443 | byte res = ReadByte();
444 |
445 | // Flip back to the original position.
446 | BaseStream.Position = origPos;
447 |
448 | return res;
449 | }
450 |
451 | ///
452 | /// Reads a signed 16-bit integer at a given offset without
453 | /// changing the underlying position.
454 | ///
455 | /// The offset to read at.
456 | /// the signed 16-bit integer read at the offset.
457 | public short ReadInt16At(long offset)
458 | {
459 | long origPos = BaseStream.Position;
460 |
461 | // Seek to the given offset
462 | BaseStream.Position = offset;
463 |
464 | // Read
465 | short res = ReadInt16();
466 |
467 | // Flip back to the original position.
468 | BaseStream.Position = origPos;
469 |
470 | return res;
471 | }
472 |
473 | ///
474 | /// Reads an unsigned 16-bit integer at a given offset without
475 | /// changing the underlying position.
476 | ///
477 | /// The offset to read at.
478 | /// the unsigned 16-bit integer read at the offset.
479 | public ushort ReadUInt16At(long offset)
480 | {
481 | long origPos = BaseStream.Position;
482 |
483 | // Seek to the given offset
484 | BaseStream.Position = offset;
485 |
486 | // Read
487 | ushort res = ReadUInt16();
488 |
489 | // Flip back to the original position.
490 | BaseStream.Position = origPos;
491 |
492 | return res;
493 | }
494 |
495 | ///
496 | /// Reads a signed 32-bit integer at a given offset without
497 | /// changing the underlying position.
498 | ///
499 | /// The offset to read at.
500 | /// the signed 32-bit integer read at the offset.
501 | public int ReadInt32At(long offset)
502 | {
503 | long origPos = BaseStream.Position;
504 |
505 | // Seek to the given offset
506 | BaseStream.Position = offset;
507 |
508 | // Read
509 | int res = ReadInt32();
510 |
511 | // Flip back to the original position.
512 | BaseStream.Position = origPos;
513 |
514 | return res;
515 | }
516 |
517 | ///
518 | /// Reads an unsigned 32-bit integer at a given offset without
519 | /// changing the underlying position.
520 | ///
521 | /// The offset to read at.
522 | /// the unsigned 32-bit integer read at the offset.
523 | public uint ReadUInt32At(long offset)
524 | {
525 | long origPos = BaseStream.Position;
526 |
527 | // Seek to the given offset
528 | BaseStream.Position = offset;
529 |
530 | // Read
531 | uint res = ReadUInt32();
532 |
533 | // Flip back to the original position.
534 | BaseStream.Position = origPos;
535 |
536 | return res;
537 | }
538 |
539 | ///
540 | /// Reads a signed 64-bit integer at a given offset without
541 | /// changing the underlying position.
542 | ///
543 | /// The offset to read at.
544 | /// the signed 64-bit integer read at the offset.
545 | public long ReadInt64At(long offset)
546 | {
547 | long origPos = BaseStream.Position;
548 |
549 | // Seek to the given offset
550 | BaseStream.Position = offset;
551 |
552 | // Read
553 | long res = ReadInt64();
554 |
555 | // Flip back to the original position.
556 | BaseStream.Position = origPos;
557 |
558 | return res;
559 | }
560 |
561 | ///
562 | /// Reads an unsigned 64-bit integer at a given offset without
563 | /// changing the underlying position.
564 | ///
565 | /// The offset to read at.
566 | /// the unsigned 64-bit integer read at the offset.
567 | public ulong ReadUInt64At(long offset)
568 | {
569 | long origPos = BaseStream.Position;
570 |
571 | // Seek to the given offset
572 | BaseStream.Position = offset;
573 |
574 | // Read
575 | ulong res = ReadUInt64();
576 |
577 | // Flip back to the original position.
578 | BaseStream.Position = origPos;
579 |
580 | return res;
581 | }
582 |
583 | ///
584 | /// Reads a 32-bit floating point integer at a given offset without
585 | /// changing the underlying position.
586 | ///
587 | /// The offset to read at.
588 | /// the 32-bit floating point integer read at the offset.
589 | public float ReadSingleAt(long offset)
590 | {
591 | long origPos = BaseStream.Position;
592 |
593 | // Seek to the given offset
594 | BaseStream.Position = offset;
595 |
596 | // Read
597 | float res = ReadSingle();
598 |
599 | // Flip back to the original position.
600 | BaseStream.Position = origPos;
601 |
602 | return res;
603 | }
604 |
605 | ///
606 | /// Reads a 64-bit floating point integer at a given offset without
607 | /// changing the underlying position.
608 | ///
609 | /// The offset to read at.
610 | /// the 64-bit floating point integer read at the offset.
611 | public double ReadDoubleAt(long offset)
612 | {
613 | long origPos = BaseStream.Position;
614 |
615 | // Seek to the given offset
616 | BaseStream.Position = offset;
617 |
618 | // Read
619 | double res = ReadDouble();
620 |
621 | // Flip back to the original position.
622 | BaseStream.Position = origPos;
623 |
624 | return res;
625 | }
626 |
627 | ///
628 | /// Reads a decimal type at a given offset without
629 | /// changing the underlying position.
630 | ///
631 | /// The offset to read at.
632 | /// the decimal type read at the offset.
633 | public decimal ReadDecimalAt(long offset)
634 | {
635 | long origPos = BaseStream.Position;
636 |
637 | // Seek to the given offset
638 | BaseStream.Position = offset;
639 |
640 | // Read
641 | decimal res = ReadDecimal();
642 |
643 | // Flip back to the original position.
644 | BaseStream.Position = origPos;
645 |
646 | return res;
647 | }
648 |
649 | ///
650 | /// Reads an character at a given offset without
651 | /// changing the underlying position.
652 | ///
653 | /// The offset to read at.
654 | /// the character read at the offset.
655 | public ulong ReadCharAt(long offset)
656 | {
657 | long origPos = BaseStream.Position;
658 |
659 | // Seek to the given offset
660 | BaseStream.Position = offset;
661 |
662 | // Read
663 | char res = ReadChar();
664 |
665 | // Flip back to the original position.
666 | BaseStream.Position = origPos;
667 |
668 | return res;
669 | }
670 |
671 | ///
672 | /// Reads a count amount of bytes starting at the given offset
673 | /// without changing the position of the underlying .
674 | ///
675 | /// Offset to begin reading at.
676 | /// Number of bytes to read.
677 | /// Byte array containing the read bytes.
678 | public byte[] ReadBytesAt(long offset, int count)
679 | {
680 | long origPos = BaseStream.Position;
681 |
682 | // Seek to the given offset
683 | BaseStream.Position = offset;
684 |
685 | // Read
686 | byte[] res = ReadBytes(count);
687 |
688 | // Flip back to the original position.
689 | BaseStream.Position = origPos;
690 |
691 | return res;
692 | }
693 |
694 | ///
695 | /// Reads a count amount of characters starting at the given offset
696 | /// without changing the position of the underlying .
697 | ///
698 | /// Offset to begin reading at.
699 | /// Number of characters to read.
700 | /// Character array containing the read characters.
701 | public char[] ReadCharsAt(long offset, int count)
702 | {
703 | long origPos = BaseStream.Position;
704 |
705 | // Seek to the given offset
706 | BaseStream.Position = offset;
707 |
708 | // Read
709 | char[] res = ReadChars(count);
710 |
711 | // Flip back to the original position.
712 | BaseStream.Position = origPos;
713 |
714 | return res;
715 | }
716 |
717 | #endregion
718 |
719 | #region Read[x]Until Methods
720 |
721 | ///
722 | /// Reads bytes from the underlying
723 | /// until the given terminating byte is hit.
724 | ///
725 | /// The terminator to stop reading at.
726 | /// The array of bytes read until the terminator was hit.
727 | public byte[] ReadBytesUntil(byte terminator)
728 | {
729 | List bytes = new List();
730 | byte b;
731 |
732 | while ((b = ReadByte()) != terminator)
733 | {
734 | bytes.Add(b);
735 | }
736 |
737 | return bytes.ToArray();
738 | }
739 |
740 | ///
741 | /// Reads characters from the underlying
742 | /// until the given terminating character is hit.
743 | ///
744 | /// The terminator to stop reading at.
745 | /// The array of characters read until the terminator was hit.
746 | public char[] ReadBytesUntil(char terminator)
747 | {
748 | List chars = new List();
749 | char c;
750 |
751 | while ((c = ReadChar()) != terminator)
752 | {
753 | chars.Add(c);
754 | }
755 |
756 | return chars.ToArray();
757 | }
758 |
759 | ///
760 | /// Reads characters from the underlying
761 | /// until the given terminating character is hit.
762 | ///
763 | /// The terminator to stop reading at.
764 | /// The string of characters read until the terminator was hit.
765 | public string ReadStringUntil(char terminator)
766 | {
767 | StringBuilder sb = new StringBuilder();
768 | char c;
769 |
770 | while ((c = ReadChar()) != terminator)
771 | {
772 | sb.Append(c);
773 | }
774 |
775 | return sb.ToString();
776 | }
777 |
778 | #endregion
779 |
780 | #region Skip[x] Methods
781 |
782 | // While some of these may be equivalents, depending on what is being read
783 | // (ie. a file format structure), it may be more readable to use one or the other.
784 | // eg. it may be more readable to say an unsigned int is being skipped than a signed int.
785 |
786 |
787 | ///
788 | /// Skips the underlying ahead by
789 | /// count bytes from its current position.
790 | ///
791 | /// The number of bytes to skip.
792 | public void Skip(long count)
793 | {
794 | if (count >= BaseStream.Length)
795 | throw new ArgumentException($"{nameof(count)} cannot be larger than the length of the underlying stream.", nameof(count));
796 |
797 | if ((BaseStream.Position + count) >= BaseStream.Length)
798 | throw new ArgumentException("Skipping " + count + " bytes would exceed the underlying stream's length.");
799 |
800 | BaseStream.Position += count;
801 | }
802 |
803 | ///
804 | /// Skips the underlying
805 | /// ahead by the size of an unsigned byte.
806 | ///
807 | public void SkipByte()
808 | {
809 | Skip(sizeof(Byte));
810 | }
811 |
812 | ///
813 | /// Skips the underlying
814 | /// ahead by the size of a signed byte.
815 | ///
816 | public void SkipSByte()
817 | {
818 | Skip(sizeof(SByte));
819 | }
820 |
821 | ///
822 | /// Skips the underlying
823 | /// ahead by the size of a signed 16-bit integer.
824 | ///
825 | public void SkipInt16()
826 | {
827 | Skip(sizeof(Int16));
828 | }
829 |
830 | ///
831 | /// Skips the underlying
832 | /// ahead by the size of an unsigned 16-bit integer.
833 | ///
834 | public void SkipUInt16()
835 | {
836 | Skip(sizeof(UInt16));
837 | }
838 |
839 | ///
840 | /// Skips the underlying
841 | /// ahead by the size of a signed 32-bit integer.
842 | ///
843 | public void SkipInt32()
844 | {
845 | Skip(sizeof(Int32));
846 | }
847 |
848 | ///
849 | /// Skips the underlying
850 | /// ahead by the size of an unsigned 32-bit integer.
851 | ///
852 | public void SkipUInt32()
853 | {
854 | Skip(sizeof(UInt32));
855 | }
856 |
857 | ///
858 | /// Skips the underlying
859 | /// ahead by the size of a signed 64-bit integer.
860 | ///
861 | public void SkipInt64()
862 | {
863 | Skip(sizeof(Int64));
864 | }
865 |
866 | ///
867 | /// Skips the underlying
868 | /// ahead by the size of an unsigned 64-bit integer.
869 | ///
870 | public void SkipUInt64()
871 | {
872 | Skip(sizeof(UInt64));
873 | }
874 |
875 | ///
876 | /// Skips the underlying
877 | /// ahead by the size of a 32-bit floating-point number.
878 | ///
879 | public void SkipSingle()
880 | {
881 | Skip(sizeof(Single));
882 | }
883 |
884 | ///
885 | /// Skips the underlying
886 | /// ahead by the size of a 64-bit floating-point number.
887 | ///
888 | public void SkipDouble()
889 | {
890 | Skip(sizeof(Double));
891 | }
892 |
893 | ///
894 | /// Skips the underlying
895 | /// ahead by the size of a 128-bit floating-point number.
896 | ///
897 | public void SkipDecimal()
898 | {
899 | Skip(sizeof(Decimal));
900 | }
901 |
902 | #endregion
903 |
904 | #region ToString
905 |
906 | public override string ToString()
907 | {
908 | string endianness = (CurrentEndian == Endian.Little) ? "Little Endian" : "Big Endian";
909 |
910 | return $"EndianBinaryReader - Endianness: {endianness}";
911 | }
912 |
913 | #endregion
914 |
915 | #endregion
916 | }
917 | }
918 |
--------------------------------------------------------------------------------
/GameFormatReader/Common/EndianBinaryWriter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 |
5 | // TODO: Write method equivalent for the decimal type.
6 | // How do you even go about flipping the endianness on this type?
7 |
8 | namespace GameFormatReader.Common
9 | {
10 | ///
11 | /// implementation that can write in different formats.
12 | ///
13 | public sealed class EndianBinaryWriter : BinaryWriter
14 | {
15 | #region Fields
16 |
17 | // Underlying endianness being used by the .NET Framework.
18 | private static readonly bool systemLittleEndian = BitConverter.IsLittleEndian;
19 |
20 | #endregion
21 |
22 | #region Properties
23 |
24 | ///
25 | /// Current this EndianBinaryReader is using.
26 | ///
27 | public Endian CurrentEndian
28 | {
29 | get;
30 | set;
31 | }
32 |
33 | #endregion
34 |
35 | #region Constructors
36 |
37 | ///
38 | /// Constructor. Uses UTF-8 by default for character encoding.
39 | ///
40 | /// The to wrap within this EndianBinaryReader.
41 | /// The to use when reading files..
42 | public EndianBinaryWriter(Stream stream, Endian endian)
43 | : base(stream)
44 | {
45 | CurrentEndian = endian;
46 | }
47 |
48 | ///
49 | /// Constructor
50 | ///
51 | /// The to wrap within this EndianBinaryReader.
52 | /// The to use for characters.
53 | /// The to use when reading files.
54 | public EndianBinaryWriter(Stream stream, Encoding encoding, Endian endian)
55 | : base(stream, encoding)
56 | {
57 | CurrentEndian = endian;
58 | }
59 |
60 | ///
61 | /// Constructor.
62 | ///
63 | /// The to wrap within this EndianBinaryReader.
64 | /// The to use for characters.
65 | /// Whether or not to leave the stream open after this EndianBinaryReader is disposed.
66 | /// The to use when reading from files.
67 | public EndianBinaryWriter(Stream stream, Encoding encoding, bool leaveOpen, Endian endian)
68 | : base(stream, encoding, leaveOpen)
69 | {
70 | CurrentEndian = endian;
71 | }
72 |
73 | #endregion
74 |
75 | #region Public Methods
76 |
77 | #region Overrides
78 |
79 | public override void Write(short value)
80 | {
81 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
82 | !systemLittleEndian && CurrentEndian == Endian.Big)
83 | {
84 | base.Write(value);
85 | }
86 | else // BE to LE or LE to BE
87 | {
88 | base.Write(value.SwapBytes());
89 | }
90 | }
91 |
92 | public override void Write(ushort value)
93 | {
94 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
95 | !systemLittleEndian && CurrentEndian == Endian.Big)
96 | {
97 | base.Write(value);
98 | }
99 | else // BE to LE or LE to BE
100 | {
101 | base.Write(value.SwapBytes());
102 | }
103 | }
104 |
105 | public override void Write(int value)
106 | {
107 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
108 | !systemLittleEndian && CurrentEndian == Endian.Big)
109 | {
110 | base.Write(value);
111 | }
112 | else // BE to LE or LE to BE
113 | {
114 | base.Write(value.SwapBytes());
115 | }
116 | }
117 |
118 | public override void Write(uint value)
119 | {
120 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
121 | !systemLittleEndian && CurrentEndian == Endian.Big)
122 | {
123 | base.Write(value);
124 | }
125 | else // BE to LE or LE to BE
126 | {
127 | base.Write(value.SwapBytes());
128 | }
129 | }
130 |
131 | public override void Write(long value)
132 | {
133 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
134 | !systemLittleEndian && CurrentEndian == Endian.Big)
135 | {
136 | base.Write(value);
137 | }
138 | else // BE to LE or LE to BE
139 | {
140 | base.Write(value.SwapBytes());
141 | }
142 | }
143 |
144 | public override void Write(ulong value)
145 | {
146 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
147 | !systemLittleEndian && CurrentEndian == Endian.Big)
148 | {
149 | base.Write(value);
150 | }
151 | else // BE to LE or LE to BE
152 | {
153 | base.Write(value.SwapBytes());
154 | }
155 | }
156 |
157 | public override void Write(float value)
158 | {
159 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
160 | !systemLittleEndian && CurrentEndian == Endian.Big)
161 | {
162 | base.Write(value);
163 | }
164 | else // BE to LE or LE to BE
165 | {
166 | byte[] floatBytes = BitConverter.GetBytes(value);
167 | Array.Reverse(floatBytes);
168 |
169 | base.Write(BitConverter.ToSingle(floatBytes, 0));
170 | }
171 | }
172 |
173 | public override void Write(double value)
174 | {
175 | if (systemLittleEndian && CurrentEndian == Endian.Little ||
176 | !systemLittleEndian && CurrentEndian == Endian.Big)
177 | {
178 | base.Write(value);
179 | }
180 | else // BE to LE or LE to BE
181 | {
182 | byte[] doubleBytes = BitConverter.GetBytes(value);
183 | Array.Reverse(doubleBytes);
184 |
185 | base.Write(BitConverter.ToDouble(doubleBytes, 0));
186 | }
187 | }
188 |
189 | #endregion
190 |
191 | #region Custom Methods
192 |
193 | ///
194 | /// Writes a specific number of characters to the underlying
195 | /// . If is greater
196 | /// than 's length, the length will be
197 | /// padded with zeros. Similarly, if is
198 | /// smaller than 's length then the string
199 | /// will be truncated.
200 | ///
201 | /// String to write to the stream.
202 | /// Maximum number of characters to write.
203 | public void WriteFixedString(string str, int length)
204 | {
205 | if (str == null)
206 | throw new ArgumentNullException(nameof(str));
207 |
208 | if (length < 0)
209 | throw new ArgumentException("Cannot write a negative length string.");
210 |
211 | for (int i = 0; i < str.Length; i++)
212 | {
213 | Write((i < str.Length) ? str[i] : '\0');
214 | }
215 | }
216 |
217 | #endregion
218 |
219 | #region ToString
220 |
221 | public override string ToString()
222 | {
223 | string endianness = (CurrentEndian == Endian.Little) ? "Little Endian" : "Big Endian";
224 |
225 | return $"EndianBinaryWriter - Endianness: {endianness}";
226 | }
227 |
228 | #endregion
229 |
230 | #endregion
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/GameFormatReader/Common/EndianUtils.cs:
--------------------------------------------------------------------------------
1 | namespace GameFormatReader.Common
2 | {
3 | ///
4 | /// Class that provides extension methods for altering endianness.
5 | ///
6 | public static class EndianUtils
7 | {
8 | public static short SwapBytes(this short x)
9 | {
10 | return (short)SwapBytes((ushort)x);
11 | }
12 |
13 | public static ushort SwapBytes(this ushort x)
14 | {
15 | return (ushort)((x & 0x00FF) << 8 |
16 | (x & 0xFF00) >> 8);
17 | }
18 |
19 | public static int SwapBytes(this int x)
20 | {
21 | return (int)SwapBytes((uint)x);
22 | }
23 |
24 | public static uint SwapBytes(this uint x)
25 | {
26 | return ((x & 0x000000FF) << 24) |
27 | ((x & 0x0000FF00) << 8) |
28 | ((x & 0x00FF0000) >> 8) |
29 | ((x & 0xFF000000) >> 24);
30 | }
31 |
32 | public static long SwapBytes(this long x)
33 | {
34 | return (long)SwapBytes((ulong)x);
35 | }
36 |
37 | public static ulong SwapBytes(this ulong x)
38 | {
39 | return (x & 0x00000000000000FFUL) << 56 |
40 | (x & 0x000000000000FF00UL) << 40 |
41 | (x & 0x0000000000FF0000UL) << 24 |
42 | (x & 0x00000000FF000000UL) << 8 |
43 | (x & 0x000000FF00000000UL) >> 8 |
44 | (x & 0x0000FF0000000000UL) >> 24 |
45 | (x & 0x00FF000000000000UL) >> 40 |
46 | (x & 0xFF00000000000000UL) >> 56;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Archive/RARC.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using GameFormatReader.Common;
5 |
6 | // TODO: A dumping method for fully extracting the tree.
7 | // As it is now, files would have to be pulled manually,
8 | // which isn't convenient at all.
9 |
10 | namespace GameFormatReader.GCWii.Archive
11 | {
12 | ///
13 | /// Represents a RARC archive file.
14 | ///
15 | public sealed class RARC
16 | {
17 | #region Private Fields
18 |
19 | // Size of the RARC header in bytes.
20 | private const int HeaderSize = 64;
21 |
22 | // Size of an individual file entry in bytes.
23 | private const int FileEntrySize = 20;
24 |
25 | #endregion
26 |
27 | #region Constructors
28 |
29 | ///
30 | /// Constructor
31 | ///
32 | /// Path to a RARC archive file.
33 | public RARC(string filepath)
34 | {
35 | if (filepath == null)
36 | throw new ArgumentNullException(nameof(filepath));
37 |
38 | if (!File.Exists(filepath))
39 | throw new FileNotFoundException($"Unable to find file: {filepath}", filepath);
40 |
41 | using (var reader = new EndianBinaryReader(File.OpenRead(filepath), Endian.Big))
42 | {
43 | ReadHeader(reader);
44 | ReadNodes(reader);
45 | }
46 | }
47 |
48 | ///
49 | /// Constructor
50 | ///
51 | /// Byte array containing RARC data.
52 | public RARC(byte[] data)
53 | {
54 | if (data == null)
55 | throw new ArgumentNullException(nameof(data));
56 |
57 | using (var reader = new EndianBinaryReader(new MemoryStream(data), Endian.Big))
58 | {
59 | ReadHeader(reader);
60 | ReadNodes(reader);
61 | }
62 | }
63 |
64 | #endregion
65 |
66 | #region Classes
67 |
68 | ///
69 | /// Represents a directory within the RARC archive.
70 | ///
71 | public sealed class Node
72 | {
73 | /// 4-character string describing the node's type.
74 | public string Type { get; internal set; }
75 | /// Directory's name.
76 | public string Name { get; internal set; }
77 | /// Hash of the field.
78 | public ushort NameHashcode { get; internal set; }
79 | /// The offset for the first file in this node.
80 | public uint FirstFileOffset { get; internal set; }
81 | /// The entries within this Node.
82 | public FileEntry[] Entries { get; internal set; }
83 |
84 | public override string ToString()
85 | {
86 | return Name;
87 | }
88 | }
89 |
90 | ///
91 | /// Represents a file or subdirectory within a RARC archive.
92 | ///
93 | public sealed class FileEntry
94 | {
95 | /// File ID. If 0xFFFF, then this entry is a subdirectory link.
96 | public ushort ID { get; internal set; }
97 | /// String hash of the field.
98 | public ushort NameHashcode { get; internal set; }
99 | /// Type of entry. 0x2 = Directory, 0x11 = File.
100 | public byte Type { get; internal set; }
101 | /// Padding byte. Included here for the sake of documentation.
102 | public byte Padding { get; internal set; }
103 | /// File/subdirectory name.
104 | public string Name { get; internal set; }
105 | /// Data bytes. If this entry is a directory, it will be the node index.
106 | public byte[] Data { get; internal set; }
107 | /// Always zero.
108 | public uint ZeroPadding { get; internal set; }
109 |
110 | // Non actual struct items
111 |
112 | /// Whether or not this entry is a directory.
113 | public bool IsDirectory { get; internal set; }
114 | /// Node index representing the subdirectory. Will only be non-zero if IsDirectory is true.
115 | public uint SubDirIndex { get; internal set; }
116 |
117 | public override string ToString()
118 | {
119 | return Name;
120 | }
121 | }
122 |
123 | #endregion
124 |
125 | #region Properties
126 |
127 | ///
128 | /// Size of this file.
129 | ///
130 | public uint FileSize
131 | {
132 | get;
133 | private set;
134 | }
135 |
136 | ///
137 | /// Offset within this archive where the data begins.
138 | ///
139 | ///
140 | /// The 0x20 value is already added internally.
141 | ///
142 | public uint DataOffset
143 | {
144 | get;
145 | private set;
146 | }
147 |
148 | ///
149 | /// Nodes within this archive
150 | ///
151 | public Node[] Nodes
152 | {
153 | get;
154 | private set;
155 | }
156 |
157 | ///
158 | /// Offset to the file entries.
159 | ///
160 | ///
161 | /// The 0x20 value is already internally added to it.
162 | ///
163 | public uint FileEntryOffset
164 | {
165 | get;
166 | private set;
167 | }
168 |
169 | ///
170 | /// Offset to the string table.
171 | ///
172 | ///
173 | /// The 0x20 value is already internally added to it.
174 | ///
175 | public uint StringTableOffset
176 | {
177 | get;
178 | private set;
179 | }
180 |
181 | #endregion
182 |
183 | #region Private Methods
184 |
185 | private void ReadHeader(EndianBinaryReader reader)
186 | {
187 | if (new string(reader.ReadChars(4)) != "RARC")
188 | throw new ArgumentException("The given data and offset does not point to a valid RARC archive.");
189 |
190 | FileSize = reader.ReadUInt32();
191 |
192 | // Skip unknown value
193 | reader.SkipUInt32();
194 |
195 | DataOffset = reader.ReadUInt32() + 0x20;
196 |
197 | // Skip unknown values.
198 | reader.Skip(16); // 4 unsigned ints.
199 |
200 | // Total number of nodes
201 | Nodes = new Node[reader.ReadUInt32()];
202 |
203 | // Skip unknown values.
204 | reader.Skip(8); // 2 unsigned ints
205 |
206 | FileEntryOffset = reader.ReadUInt32() + 0x20;
207 |
208 | // Skip unknown value
209 | reader.SkipUInt32();
210 |
211 | StringTableOffset = reader.ReadUInt32() + 0x20;
212 |
213 | // Skip unknown values
214 | reader.Skip(8); // 2 unsigned ints
215 | }
216 |
217 | // Reads the node tree. This includes the file entries.
218 | private void ReadNodes(EndianBinaryReader reader)
219 | {
220 | // Nodes begin right after the header.
221 | reader.BaseStream.Position = HeaderSize;
222 |
223 | for (int i = 0; i < Nodes.Length; i++)
224 | {
225 | Nodes[i] = new Node();
226 | Nodes[i].Type = new string(reader.ReadChars(4));
227 | Nodes[i].Name = ReadString(reader, reader.ReadUInt32());
228 | Nodes[i].NameHashcode = reader.ReadUInt16();
229 | Nodes[i].Entries = new FileEntry[reader.ReadUInt16()];
230 | Nodes[i].FirstFileOffset = reader.ReadUInt32();
231 | }
232 |
233 | // Now read the node entries.
234 | foreach (Node node in Nodes)
235 | {
236 | for (int i = 0; i < node.Entries.Length; i++)
237 | {
238 | // Find the entry position
239 | reader.BaseStream.Position = FileEntryOffset + ((node.FirstFileOffset + i) * FileEntrySize);
240 |
241 | node.Entries[i] = new FileEntry();
242 | node.Entries[i].ID = reader.ReadUInt16();
243 | node.Entries[i].NameHashcode = reader.ReadUInt16();
244 | node.Entries[i].Type = reader.ReadByte();
245 | node.Entries[i].Padding = reader.ReadByte();
246 | node.Entries[i].Name = ReadString(reader, reader.ReadUInt16());
247 | node.Entries[i].IsDirectory = (node.Entries[i].ID == 0xFFFF);
248 |
249 | uint entryDataOffset = reader.ReadUInt32();
250 | uint dataSize = reader.ReadUInt32();
251 |
252 | // If it's a Directory, then entryDataOffset contains
253 | // the index of the parent node.
254 | if (node.Entries[i].IsDirectory)
255 | {
256 | node.Entries[i].SubDirIndex = entryDataOffset;
257 | }
258 | else // It's a file, get the data.
259 | {
260 | node.Entries[i].Data = reader.ReadBytesAt(DataOffset + entryDataOffset, (int) dataSize);
261 | }
262 |
263 | node.Entries[i].ZeroPadding = reader.ReadUInt32();
264 | }
265 | }
266 | }
267 |
268 | // Reads a string from the string table.
269 | private string ReadString(EndianBinaryReader reader, uint offset)
270 | {
271 | // Save position, and seek to string table
272 | long curPos = reader.BaseStream.Position;
273 | uint stringOffset = offset+StringTableOffset;
274 | reader.BaseStream.Position = stringOffset;
275 |
276 | byte[] bytes = reader.ReadBytesUntil(0x00);
277 | string result = Encoding.GetEncoding("shift_jis").GetString(bytes);
278 |
279 | // Seek back to previous position.
280 | reader.BaseStream.Position = curPos;
281 | return result;
282 | }
283 |
284 | #endregion
285 | }
286 | }
287 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Archive/U8.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using GameFormatReader.Common;
6 |
7 | namespace GameFormatReader.GCWii.Archive
8 | {
9 | ///
10 | /// U8 archive structure handling.
11 | ///
12 | public static class U8
13 | {
14 | #region Constants
15 |
16 | private const int HeaderSize = 0x20;
17 | private const int NodeSize = 0x0C;
18 |
19 | #endregion
20 |
21 | #region Header Class
22 |
23 | // Represents the relevant sections of the header
24 | private sealed class Header
25 | {
26 | /// Size of the header from the root node until the end of the string table
27 | public uint TotalDataSize { get; set; }
28 |
29 | /// Offset to the data. This is root node offset + HeaderSize, aligned to 0x40.
30 | public uint DataOffset { get; private set; }
31 |
32 | ///
33 | /// Constructor
34 | ///
35 | /// used to get relevant data.
36 | public Header(EndianBinaryReader reader)
37 | {
38 | // Skip header magic and root node offset (offset is always 0x20).
39 | reader.BaseStream.Position += 8;
40 |
41 | TotalDataSize = reader.ReadUInt32();
42 | DataOffset = reader.ReadUInt32();
43 |
44 | // Skip zeroed bytes
45 | reader.BaseStream.Position += 16;
46 | }
47 | }
48 |
49 | #endregion
50 |
51 | #region Node Class
52 |
53 | private sealed class Node
54 | {
55 | ///
56 | /// Node type
57 | ///
58 | public byte Type { get; private set; }
59 |
60 | ///
61 | /// Offset into the string table where the file's name is.
62 | ///
63 | public ushort NameOffset { get; private set; }
64 |
65 | ///
66 | /// Binary data for this node.
67 | ///
68 | public byte[] Data { get; private set; }
69 |
70 | ///
71 | /// Size of the file this node represents.
72 | ///
73 | /// For Directories, this is the last file number.
74 | ///
75 | ///
76 | /// This is a 24-bit number encoded within a 32-bit space.
77 | ///
78 | public int Size { get; private set; }
79 |
80 | ///
81 | /// Constructor
82 | ///
83 | /// used to get relevant data.
84 | public Node(EndianBinaryReader reader)
85 | {
86 | // Type is written as an unsigned short, but it really just a byte.
87 | Type = reader.ReadByte();
88 | reader.SkipByte();
89 |
90 | NameOffset = reader.ReadUInt16();
91 |
92 | var dataOffset = reader.ReadUInt32();
93 | Size = reader.ReadInt32() & 0x00FFFFFF;
94 |
95 | Data = reader.ReadBytesAt(dataOffset, Size);
96 | }
97 | }
98 |
99 | #endregion
100 |
101 | #region Public Extraction Methods
102 |
103 | ///
104 | /// Extracts the U8 file structure from the file
105 | /// indicated by the given file path.
106 | ///
107 | /// Path to the file to extract the U8 file structure from.
108 | /// Root output directory to place the extracted file hierarchy.
109 | /// True if extraction was successful, otherwise false.
110 | public static bool Extract(string filepath, string outputDir)
111 | {
112 | if (string.IsNullOrEmpty(filepath) || string.IsNullOrEmpty(outputDir))
113 | return false;
114 |
115 | if (!IsValidU8(filepath))
116 | return false;
117 |
118 | return ExtractionProcess(File.ReadAllBytes(filepath), outputDir);
119 | }
120 |
121 | ///
122 | /// Extracts the U8 file structure from a byte array in memory.
123 | ///
124 | /// Byte array containing the U8 file structure.
125 | /// Root output directory to place the extracted file hierarchy.
126 | /// True if extraction was successful, otherwise false.
127 | public static bool Extract(byte[] data, string outputDir)
128 | {
129 | if (string.IsNullOrEmpty(outputDir))
130 | return false;
131 |
132 | if (data == null)
133 | return false;
134 |
135 | if (!IsValidU8(data))
136 | return false;
137 |
138 | return ExtractionProcess(data, outputDir);
139 | }
140 |
141 | #endregion
142 |
143 | #region Public Utility Functions
144 |
145 | ///
146 | /// Utility function for checking if a given file is U8 compressed.
147 | ///
148 | /// Path to the file to check.
149 | /// True if the file is a valid U8 archive, otherwise false.
150 | public static bool IsValidU8(string filepath)
151 | {
152 | if (string.IsNullOrEmpty(filepath))
153 | return false;
154 |
155 | if (!File.Exists(filepath))
156 | return false;
157 |
158 | using (var reader = new BinaryReader(File.OpenRead(filepath)))
159 | {
160 | // Check for a malformed header
161 | if (reader.BaseStream.Length < HeaderSize)
162 | return false;
163 |
164 | byte[] data = reader.ReadBytes(4);
165 |
166 | return data[0] == 0x55 &&
167 | data[1] == 0xAA &&
168 | data[2] == 0x38 &&
169 | data[3] == 0x2D;
170 | }
171 | }
172 |
173 | ///
174 | /// Utility function for checking if given data is using a U8 file structure.
175 | ///
176 | /// The data to check.
177 | /// True if is a valid U8 archive, otherwise false.
178 | public static bool IsValidU8(byte[] data)
179 | {
180 | if (data == null)
181 | return false;
182 |
183 | // Check for a malformed header
184 | if (data.Length < HeaderSize)
185 | return false;
186 |
187 | return data[0] == 0x55 &&
188 | data[1] == 0xAA &&
189 | data[2] == 0x38 &&
190 | data[3] == 0x2D;
191 | }
192 |
193 | #endregion
194 |
195 | #region Private Helper Methods
196 |
197 | private static bool ExtractionProcess(byte[] data, string outputDir)
198 | {
199 | using (var reader = new EndianBinaryReader(new MemoryStream(data), Endian.Big))
200 | {
201 | var header = new Header(reader);
202 | var rootNode = new Node(reader);
203 |
204 | // Root node specifies the total amount of nodes
205 | var nodes = new Node[rootNode.Size - 1];
206 | for (int i = 0; i < nodes.Length; i++)
207 | nodes[i] = new Node(reader);
208 |
209 | byte[] stringTable = reader.ReadBytes((int) (header.DataOffset - HeaderSize - rootNode.Size * NodeSize));
210 |
211 | return WriteNodes(nodes, stringTable, outputDir);
212 | }
213 | }
214 |
215 | private static bool WriteNodes(Node[] nodes, byte[] stringTable, string outputDir)
216 | {
217 | // Create the root output directory
218 | if (!AttemptToCreateDirectory(outputDir))
219 | return false;
220 |
221 | var currentDirectory = outputDir;
222 |
223 | // Capacity of 1 to accommodate the root directory.
224 | var directoryIndex = 0;
225 | var directorySizes = new List(new int[1]);
226 |
227 | for (int i = 0; i < nodes.Length; i++)
228 | {
229 | Node node = nodes[i];
230 |
231 | string name = GetStringFromStringTable(node.NameOffset, stringTable);
232 |
233 | // File node
234 | if (node.Type == 0x00)
235 | {
236 | using (var fileStream = File.Create(Path.Combine(currentDirectory, name)))
237 | {
238 | fileStream.Write(node.Data, 0, node.Data.Length);
239 | }
240 | }
241 | // Directory node
242 | else if (node.Type == 0x01)
243 | {
244 | directorySizes.Add(node.Size);
245 | directoryIndex++;
246 | currentDirectory = Path.Combine(currentDirectory, name);
247 |
248 | if (!AttemptToCreateDirectory(currentDirectory))
249 | return false;
250 | }
251 |
252 | while (directorySizes[directoryIndex] == i+2 && directoryIndex > 0)
253 | {
254 | currentDirectory = Directory.GetParent(currentDirectory).FullName;
255 |
256 | // Pop the back of the list.
257 | directorySizes.RemoveAt(directorySizes.Count - 1);
258 | directoryIndex--;
259 | }
260 | }
261 |
262 | return true;
263 | }
264 |
265 | private static string GetStringFromStringTable(int nameOffset, byte[] stringTable)
266 | {
267 | // String tables within U8 archives use null-terminated strings.
268 | int nullCharIndex = Array.FindIndex(stringTable, nameOffset, x => x == '\0');
269 | byte[] stringBytes = new byte[nullCharIndex - nameOffset];
270 |
271 | Array.Copy(stringTable, nameOffset, stringBytes, 0, stringBytes.Length);
272 |
273 | return Encoding.UTF8.GetString(stringBytes);
274 | }
275 |
276 | private static bool AttemptToCreateDirectory(string directory)
277 | {
278 | try
279 | {
280 | Directory.CreateDirectory(directory);
281 | return true;
282 | }
283 | catch (IOException)
284 | {
285 | return false;
286 | }
287 | }
288 |
289 | #endregion
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Binaries/DOL.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using GameFormatReader.Common;
4 |
5 | namespace GameFormatReader.GCWii.Binaries
6 | {
7 | ///
8 | /// Represents a GameCube executable.
9 | ///
10 | public sealed class DOL
11 | {
12 | #region Constructors
13 |
14 | ///
15 | /// Constructor
16 | ///
17 | /// Path to the DOL file.
18 | public DOL(string filepath)
19 | {
20 | if (filepath == null)
21 | throw new ArgumentNullException(nameof(filepath));
22 |
23 | if (!File.Exists(filepath))
24 | throw new FileNotFoundException($"File {filepath} does not exist.", filepath);
25 |
26 | ReadDOL(filepath);
27 | }
28 |
29 | ///
30 | /// Constructor
31 | ///
32 | /// Data that contains the DOL.
33 | /// Offset in the data to begin reading at.
34 | public DOL(byte[] data, int offset)
35 | {
36 | if (data == null)
37 | throw new ArgumentNullException(nameof(data));
38 |
39 | if (offset < 0)
40 | throw new ArgumentOutOfRangeException(nameof(offset), $"{nameof(offset)} cannot be less than zero.");
41 |
42 | if (offset >= data.Length)
43 | throw new ArgumentOutOfRangeException(nameof(offset), $"{nameof(offset)} cannot be larger than the given data.");
44 |
45 | ReadDOL(data, offset);
46 | }
47 |
48 | #endregion
49 |
50 | #region Structs
51 |
52 | ///
53 | /// Represents an arbitrary section within a DOL.
54 | ///
55 | public struct Section
56 | {
57 | /// File offset within the DOL for this section.
58 | public uint FileOffset { get; internal set; }
59 | /// Load address for this section
60 | public uint LoadAddress { get; internal set; }
61 | /// Section data
62 | public byte[] Data { get; internal set; }
63 | }
64 |
65 | #endregion
66 |
67 | #region Properties
68 |
69 | ///
70 | /// The data sections within this DOL.
71 | ///
72 | public Section[] DataSections
73 | {
74 | get;
75 | private set;
76 | }
77 |
78 | ///
79 | /// The text sections within this DOL.
80 | ///
81 | public Section[] TextSections
82 | {
83 | get;
84 | private set;
85 | }
86 |
87 | ///
88 | /// Stored BSS address
89 | ///
90 | public uint BSSAddress
91 | {
92 | get;
93 | private set;
94 | }
95 |
96 | ///
97 | /// Size of the BSS
98 | ///
99 | public uint BSSSize
100 | {
101 | get;
102 | private set;
103 | }
104 |
105 | ///
106 | /// DOL entry point
107 | ///
108 | public uint EntryPoint
109 | {
110 | get;
111 | private set;
112 | }
113 |
114 | #endregion
115 |
116 | #region Private
117 |
118 | private void ReadDOL(string filepath)
119 | {
120 | byte[] dolBytes = File.ReadAllBytes(filepath);
121 |
122 | ReadDOL(dolBytes, 0);
123 | }
124 |
125 | private void ReadDOL(byte[] data, int offset)
126 | {
127 | using (var reader = new EndianBinaryReader(new MemoryStream(data), Endian.Big))
128 | {
129 | InitializeTextSections(reader);
130 | InitializeDataSections(reader);
131 |
132 | // Skip to BSS stuff.
133 | reader.BaseStream.Position = 0xD8;
134 | BSSAddress = reader.ReadUInt32();
135 | BSSSize = reader.ReadUInt32();
136 | EntryPoint = reader.ReadUInt32();
137 | }
138 | }
139 |
140 | private void InitializeTextSections(EndianBinaryReader reader)
141 | {
142 | TextSections = new Section[7];
143 |
144 | for (int i = 0; i < TextSections.Length; i++)
145 | {
146 | TextSections[i] = new Section();
147 | TextSections[i].FileOffset = reader.ReadUInt32At(0x00 + (i*4)); // 4 == length of offset value
148 | TextSections[i].LoadAddress = reader.ReadUInt32At(0x48 + (i*4));
149 | uint size = reader.ReadUInt32At(0x90 + (i*4));
150 |
151 | TextSections[i].Data = reader.ReadBytesAt(TextSections[i].FileOffset, (int)size);
152 | }
153 | }
154 |
155 | private void InitializeDataSections(EndianBinaryReader reader)
156 | {
157 | DataSections = new Section[11];
158 |
159 | for (int i = 0; i < DataSections.Length; i++)
160 | {
161 | DataSections[i] = new Section();
162 | DataSections[i].FileOffset = reader.ReadUInt32At(0x1C + (i*4));
163 | DataSections[i].LoadAddress = reader.ReadUInt32At(0x64 + (i*4));
164 | uint size = reader.ReadUInt32At(0xAC + (i*4));
165 |
166 | DataSections[i].Data = reader.ReadBytesAt(DataSections[i].FileOffset, (int)size);
167 | }
168 | }
169 |
170 | #endregion
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Compression/Yaz0.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using GameFormatReader.Common;
4 |
5 | namespace GameFormatReader.GCWii.Compression
6 | {
7 | ///
8 | /// Yaz0 compression format utility functions.
9 | ///
10 | public static class Yaz0
11 | {
12 | // Position where the compressed data starts.
13 | private const int StartOfData = 0x10;
14 |
15 | ///
16 | /// Decodes a given Yaz0-compressed file.
17 | ///
18 | /// Path to the Yaz0 compressed file.
19 | /// the decoded data.
20 | public static byte[] Decode(string filepath)
21 | {
22 | if (filepath == null)
23 | throw new ArgumentNullException(nameof(filepath));
24 |
25 | if (!File.Exists(filepath))
26 | throw new FileNotFoundException($"File {filepath} does not exist.", filepath);
27 |
28 | return Decode(File.ReadAllBytes(filepath));
29 | }
30 |
31 | ///
32 | /// Decodes the given source data.
33 | ///
34 | /// Decoding alg borrowed from YAGCD..
35 | /// The source data to decode.
36 | /// The decoded data.
37 | public static byte[] Decode(byte[] input)
38 | {
39 | if (input == null)
40 | throw new ArgumentNullException(nameof(input));
41 |
42 | using (var reader = new EndianBinaryReader(new MemoryStream(input), Endian.Big))
43 | {
44 | // Check if the data is even Yaz0 compressed.
45 | if (new string(reader.ReadChars(4)) != "Yaz0")
46 | throw new ArgumentException($"{input} is not Yaz0 compressed data", nameof(input));
47 |
48 | // These 4 bytes read tell us the decompressed data size.
49 | byte[] output = new byte[reader.ReadUInt32()];
50 |
51 | // Now decode the compressed data.
52 | long srcPos = StartOfData;
53 | long dstPos = 0;
54 | uint validBitCount = 0; // Number of valid bits left in the 'code' byte.
55 | byte currentCodeByte = 0;
56 |
57 | while (dstPos < output.Length)
58 | {
59 | if (validBitCount == 0)
60 | {
61 | currentCodeByte = input[srcPos++];
62 | validBitCount = 8;
63 | }
64 |
65 | if ((currentCodeByte & 0x80) != 0)
66 | {
67 | // Straight copy
68 | output[dstPos++] = input[srcPos++];
69 | }
70 | else
71 | {
72 | // RLE part.
73 | byte byte1 = input[srcPos++];
74 | byte byte2 = input[srcPos++];
75 |
76 | uint dist = (uint) (((byte1 & 0x0F) << 8) | byte2);
77 | uint copySrc = (uint) (dstPos - (dist + 1));
78 | uint numBytes = (uint) (byte1 >> 4);
79 |
80 | if (numBytes == 0)
81 | {
82 | numBytes = input[srcPos++] + 0x12U;
83 | }
84 | else
85 | {
86 | numBytes += 2;
87 | }
88 |
89 | // Copy run
90 | for (int i = 0; i < numBytes; i++)
91 | {
92 | output[dstPos++] = output[copySrc++];
93 | }
94 | }
95 |
96 | // Use next bit from the "code" byte.
97 | currentCodeByte <<= 1;
98 | validBitCount -= 1;
99 | }
100 |
101 | return output;
102 | }
103 | }
104 |
105 | ///
106 | /// Checks if a given file is Yaz0-compressed.
107 | ///
108 | /// Path to the file to check.
109 | /// true if the file is Yaz0-compressed; false otherwise.
110 | public static bool IsYaz0Compressed(string filePath)
111 | {
112 | if (filePath == null)
113 | throw new ArgumentNullException(nameof(filePath));
114 |
115 | if (!File.Exists(filePath))
116 | throw new FileNotFoundException($"File {filePath} does not exist.", filePath);
117 |
118 | using (var fs = File.OpenRead(filePath))
119 | {
120 | byte[] magic = new byte[4];
121 | fs.Read(magic, 0, 4);
122 |
123 | return magic[0] == 'Y' &&
124 | magic[1] == 'a' &&
125 | magic[2] == 'z' &&
126 | magic[3] == '0';
127 | }
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Discs/Disc.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace GameFormatReader.GCWii.Discs
5 | {
6 | ///
7 | /// Represents a GameCube or Wii disc.
8 | ///
9 | public abstract class Disc
10 | {
11 | #region Constructors
12 |
13 | ///
14 | /// Constructor.
15 | ///
16 | /// Path to a disc.
17 | protected Disc(string filepath)
18 | {
19 | if (filepath == null)
20 | throw new ArgumentNullException(nameof(filepath));
21 |
22 | if (!File.Exists(filepath))
23 | throw new FileNotFoundException($"File {filepath} does not exist.", filepath);
24 | }
25 |
26 | #endregion
27 |
28 | #region Properties
29 |
30 | ///
31 | /// The for this disc.
32 | ///
33 | public abstract DiscHeader Header
34 | {
35 | get;
36 | protected set;
37 | }
38 |
39 | #endregion
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Discs/DiscHeader.cs:
--------------------------------------------------------------------------------
1 | using GameFormatReader.Common;
2 |
3 | namespace GameFormatReader.GCWii.Discs
4 | {
5 | ///
6 | /// Represents a disc header of a GameCube and Wii disc.
7 | ///
8 | ///
9 | /// The basic header is always 1024 bytes.
10 | /// However, only the first 400 bytes provide relevant info.
11 | ///
12 | public abstract class DiscHeader
13 | {
14 | #region Properties
15 |
16 | ///
17 | /// The of this .
18 | ///
19 | public DiscType Type
20 | {
21 | get;
22 | protected set;
23 | }
24 |
25 | ///
26 | /// Whether or not this disc is a Gamecube disc.
27 | ///
28 | public bool IsGamecubeDisc
29 | {
30 | get;
31 | protected set;
32 | }
33 |
34 | ///
35 | /// The embedded game code on this .
36 | ///
37 | public string GameCode
38 | {
39 | get;
40 | protected set;
41 | }
42 |
43 | ///
44 | /// The code for this .
45 | ///
46 | public Region RegionCode
47 | {
48 | get;
49 | protected set;
50 | }
51 |
52 | ///
53 | /// The MakerCode for this .
54 | ///
55 | public string MakerCode
56 | {
57 | get;
58 | protected set;
59 | }
60 |
61 | ///
62 | /// Disc number for this .
63 | ///
64 | ///
65 | /// This is usually used only by multi-disc games.
66 | ///
67 | public int DiscNumber
68 | {
69 | get;
70 | protected set;
71 | }
72 |
73 | ///
74 | /// Whether or not this utilizes audio streaming.
75 | ///
76 | public bool AudioStreaming
77 | {
78 | get;
79 | protected set;
80 | }
81 |
82 | ///
83 | /// Size of the audio streaming buffer.
84 | ///
85 | public int StreamingBufferSize
86 | {
87 | get;
88 | protected set;
89 | }
90 |
91 | ///
92 | /// Magic word that identifies whether or not this
93 | /// disc is a Wii or GameCube disc.
94 | ///
95 | public int MagicWord
96 | {
97 | get;
98 | protected set;
99 | }
100 |
101 | ///
102 | /// Title of the game on this .
103 | ///
104 | public string GameTitle
105 | {
106 | get;
107 | protected set;
108 | }
109 |
110 | #endregion
111 |
112 | #region Protected Methods
113 |
114 | protected static DiscType DetermineDiscType(char id)
115 | {
116 | switch (id)
117 | {
118 | case 'R':
119 | return DiscType.Revolution;
120 |
121 | case 'S':
122 | return DiscType.Wii;
123 |
124 | case 'G':
125 | return DiscType.Gamecube;
126 |
127 | case 'U':
128 | return DiscType.Utility;
129 |
130 | case 'D':
131 | return DiscType.GamecubeDemo;
132 |
133 | case 'P':
134 | return DiscType.GamecubePromotional;
135 |
136 | case '0':
137 | return DiscType.Diagnostic;
138 |
139 | case '1':
140 | return DiscType.Diagnostic2;
141 |
142 | case '4':
143 | return DiscType.WiiBackup;
144 |
145 | case '_':
146 | return DiscType.WiiFitChanInstaller;
147 |
148 | default:
149 | return DiscType.Unknown;
150 | }
151 | }
152 |
153 | protected static Region DetermineRegion(char id)
154 | {
155 | switch (id)
156 | {
157 | case 'U':
158 | return Region.Australia;
159 |
160 | case 'F':
161 | return Region.France;
162 |
163 | case 'D':
164 | return Region.Germany;
165 |
166 | case 'I':
167 | return Region.Italy;
168 |
169 | case 'J':
170 | return Region.Japan;
171 |
172 | case 'K':
173 | return Region.Korea;
174 |
175 | case 'P':
176 | return Region.PAL;
177 |
178 | case 'R':
179 | return Region.Russia;
180 |
181 | case 'S':
182 | return Region.Spanish;
183 |
184 | case 'T':
185 | return Region.Taiwan;
186 |
187 | case 'E':
188 | return Region.USA;
189 |
190 | default:
191 | return Region.Unknown;
192 | }
193 | }
194 |
195 | #endregion
196 |
197 | #region Abstract Methods
198 |
199 | ///
200 | /// Reads the console specific disc header.
201 | ///
202 | /// The used to read the disc.
203 | protected abstract void ReadHeader(EndianBinaryReader reader);
204 |
205 | #endregion
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Discs/DiscType.cs:
--------------------------------------------------------------------------------
1 | namespace GameFormatReader.GCWii.Discs
2 | {
3 | ///
4 | /// The type a can be.
5 | ///
6 | public enum DiscType
7 | {
8 | Unknown,
9 | Revolution, // 'R'
10 | Wii, // 'S'
11 | Gamecube, // 'G'
12 | Utility, // 'U'
13 | GamecubeDemo, // 'D'
14 | GamecubePromotional, // 'P'
15 | Diagnostic, // '0'
16 | Diagnostic2, // '1'
17 | WiiBackup, // '4'
18 | WiiFitChanInstaller, // '_'
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Discs/GC/Apploader.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using GameFormatReader.Common;
3 |
4 | namespace GameFormatReader.GCWii.Discs.GC
5 | {
6 | ///
7 | /// Represents the Apploader on GameCube s.
8 | ///
9 | public sealed class Apploader
10 | {
11 | #region Private Fields
12 |
13 | private const int ApploaderOffset = 0x2440;
14 | private const int HeaderSize = 0x20;
15 |
16 | #endregion
17 |
18 | #region Constructor
19 |
20 | internal Apploader(EndianBinaryReader reader)
21 | {
22 | reader.BaseStream.Position = ApploaderOffset;
23 |
24 | Version = new string(reader.ReadChars(10));
25 |
26 | // Skip padding
27 | reader.BaseStream.Position += 6;
28 |
29 | EntryPoint = reader.ReadInt32();
30 | Size = reader.ReadInt32();
31 | TrailerSize = reader.ReadInt32();
32 |
33 | // Seek back and pull all the data.
34 | reader.BaseStream.Position = ApploaderOffset;
35 | int apploaderSize = HeaderSize + Size + TrailerSize;
36 | Data = reader.ReadBytes(apploaderSize);
37 | }
38 |
39 | #endregion
40 |
41 | #region Properties
42 |
43 | ///
44 | /// Apploader Version
45 | ///
46 | public string Version
47 | {
48 | get;
49 | private set;
50 | }
51 |
52 | ///
53 | /// Apploader entry point
54 | ///
55 | public int EntryPoint
56 | {
57 | get;
58 | private set;
59 | }
60 |
61 | ///
62 | /// Apploader size
63 | ///
64 | public int Size
65 | {
66 | get;
67 | private set;
68 | }
69 |
70 | ///
71 | /// Trailer size
72 | ///
73 | public int TrailerSize
74 | {
75 | get;
76 | private set;
77 | }
78 |
79 | ///
80 | /// The entire Apploader data chunk.
81 | ///
82 | public byte[] Data
83 | {
84 | get;
85 | private set;
86 | }
87 |
88 | #endregion
89 |
90 | #region Public Methods
91 |
92 | ///
93 | /// Saves the embedded Apploader to a given output path.
94 | ///
95 | /// The path to save the apploader to.
96 | /// true if the file was saved successfully, false otherwise.
97 | public bool Save(string output)
98 | {
99 | try
100 | {
101 | using (FileStream fs = new FileStream(output, FileMode.Open, FileAccess.Write))
102 | {
103 | fs.Write(Data, 0, Data.Length);
104 | }
105 | }
106 | catch (IOException)
107 | {
108 | return false;
109 | }
110 |
111 | return true;
112 | }
113 |
114 | #endregion
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Discs/GC/DiscGC.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using GameFormatReader.Common;
3 |
4 | namespace GameFormatReader.GCWii.Discs.GC
5 | {
6 | ///
7 | /// Represents a GameCube .
8 | ///
9 | public sealed class DiscGC : Disc
10 | {
11 | #region Constructors
12 |
13 | ///
14 | /// Constructor
15 | ///
16 | /// Path to the GameCube .
17 | public DiscGC(string filepath) : base(filepath)
18 | {
19 | using (var reader = new EndianBinaryReader(File.OpenRead(filepath), Endian.Big))
20 | {
21 | Header = new DiscHeaderGC(reader);
22 | Apploader = new Apploader(reader);
23 | FileSystemTable = new FST(reader);
24 | }
25 | }
26 |
27 | #endregion
28 |
29 | #region Properties
30 |
31 | ///
32 | /// GameCube .
33 | ///
34 | public override DiscHeader Header
35 | {
36 | get;
37 | protected set;
38 | }
39 |
40 | ///
41 | /// The Apploader on this GameCube .
42 | ///
43 | public Apploader Apploader
44 | {
45 | get;
46 | private set;
47 | }
48 |
49 | ///
50 | /// File-system table of this disc.
51 | ///
52 | public FST FileSystemTable
53 | {
54 | get;
55 | private set;
56 | }
57 |
58 | #endregion
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Discs/GC/DiscHeaderGC.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using GameFormatReader.Common;
3 |
4 | namespace GameFormatReader.GCWii.Discs.GC
5 | {
6 | ///
7 | /// Represents a for the GameCube.
8 | /// Mostly the same as the Wii, except for some minor differences.
9 | ///
10 | public sealed class DiscHeaderGC : DiscHeader
11 | {
12 | #region Constructor
13 |
14 | internal DiscHeaderGC(EndianBinaryReader reader)
15 | {
16 | ReadHeader(reader);
17 | }
18 |
19 | #endregion
20 |
21 | #region Properties
22 |
23 | ///
24 | /// Offset of the debug monitor (dh.bin).
25 | ///
26 | public uint DebugMonitorOffset
27 | {
28 | get;
29 | private set;
30 | }
31 |
32 | ///
33 | /// Load address for the debug monitor.
34 | ///
35 | public uint DebugMonitorLoadAddress
36 | {
37 | get;
38 | private set;
39 | }
40 |
41 | ///
42 | /// Offset to the main DOL executable.
43 | ///
44 | public uint MainDolOffset
45 | {
46 | get;
47 | private set;
48 | }
49 |
50 | ///
51 | /// Offset to the FST.
52 | ///
53 | public uint FSTOffset
54 | {
55 | get;
56 | private set;
57 | }
58 |
59 | ///
60 | /// Size of the FST.
61 | ///
62 | public uint FSTSize
63 | {
64 | get;
65 | private set;
66 | }
67 |
68 | ///
69 | /// Maximum size of the FST.
70 | /// Usually the same size as FSTSize.
71 | ///
72 | public uint MaxFSTSize
73 | {
74 | get;
75 | private set;
76 | }
77 |
78 | #endregion
79 |
80 | #region Private Methods
81 |
82 | // Reads GameCube specific header info.
83 | protected override void ReadHeader(EndianBinaryReader reader)
84 | {
85 | Type = DetermineDiscType(reader.ReadChar());
86 | GameCode = new string(reader.ReadChars(2));
87 | RegionCode = DetermineRegion(reader.ReadChar());
88 | MakerCode = new string(reader.ReadChars(2));
89 | DiscNumber = reader.ReadByte();
90 | AudioStreaming = reader.ReadBoolean();
91 | StreamingBufferSize = reader.ReadByte();
92 |
93 | // Skip unused bytes
94 | reader.BaseStream.Position += 12;
95 |
96 | MagicWord = reader.ReadInt32();
97 |
98 | // Skip to game title. Read until 0x00 (null char) is hit.
99 | reader.BaseStream.Position = 0x20;
100 | GameTitle = Encoding.GetEncoding("shift_jis").GetString(reader.ReadBytesUntil(0x00));
101 |
102 | DebugMonitorOffset = reader.ReadUInt32();
103 | DebugMonitorLoadAddress = reader.ReadUInt32();
104 |
105 | // Skip unused bytes
106 | reader.BaseStream.Position = 0x420;
107 |
108 | MainDolOffset = reader.ReadUInt32();
109 | FSTOffset = reader.ReadUInt32();
110 | FSTSize = reader.ReadUInt32();
111 | MaxFSTSize = reader.ReadUInt32();
112 | }
113 |
114 | #endregion
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Discs/GC/FST.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using GameFormatReader.Common;
5 |
6 | namespace GameFormatReader.GCWii.Discs.GC
7 | {
8 | ///
9 | /// File-system Table. Contains all of the files within a GameCube .
10 | ///
11 | public sealed class FST : IEnumerable
12 | {
13 | #region Private Fields
14 |
15 | private readonly List fileEntries = new List();
16 |
17 | #endregion
18 |
19 | #region Constructor
20 |
21 | ///
22 | /// Constructor
23 | ///
24 | /// Reader to populate the FST with.
25 | internal FST(EndianBinaryReader reader)
26 | {
27 | InitializeFST(reader);
28 | }
29 |
30 | #endregion
31 |
32 | #region Private Methods
33 |
34 | // Initializes the FST and gets all the files and folders set up.
35 | private void InitializeFST(EndianBinaryReader reader)
36 | {
37 | reader.BaseStream.Position = 0x424; // FST offset value is stored here.
38 | uint FSTOffset = reader.ReadUInt32();
39 | reader.BaseStream.Position = FSTOffset;
40 |
41 | // We use this to find the string table offset that follows after all the files.
42 | uint stringTableOffset = FSTOffset;
43 |
44 | // Now get the root.
45 | FSTEntry root = new FSTEntry();
46 | root.NameOffset = reader.ReadUInt32();
47 | root.Offset = reader.ReadUInt32();
48 | root.FileSize = reader.ReadUInt32();
49 |
50 | for (uint i = 0; i < root.FileSize; i++)
51 | {
52 | // Next file = base FST offset + (current file index * byte size of an individual entry).
53 | uint offset = FSTOffset + (i * FSTEntry.EntrySize);
54 | reader.BaseStream.Position = offset;
55 |
56 | FSTEntry file = new FSTEntry();
57 | file.NameOffset = reader.ReadUInt32();
58 | file.Offset = reader.ReadUInt32();
59 | file.FileSize = reader.ReadUInt32();
60 |
61 | // Add to overall files present in FST.
62 | fileEntries.Add(file);
63 |
64 | // Increment tentative string table offset.
65 | stringTableOffset += FSTEntry.EntrySize;
66 | }
67 |
68 | BuildFilenames(reader, 1, fileEntries.Count, "", stringTableOffset);
69 | }
70 |
71 | // Reads the string table and assigns the correct names to FST entries.
72 | private int BuildFilenames(EndianBinaryReader reader, int firstIndex, int lastIndex, string directory, uint stringTableOffset)
73 | {
74 | int currentIndex = firstIndex;
75 |
76 | while (currentIndex < lastIndex)
77 | {
78 | FSTEntry entry = fileEntries[currentIndex];
79 | uint tableOffset = stringTableOffset + (entry.NameOffset & 0xFFFFFF);
80 | reader.BaseStream.Position = tableOffset;
81 |
82 | // Null char is a terminator in the string table, so read until we hit it.
83 | string filename = Encoding.GetEncoding("shift_jis").GetString(reader.ReadBytesUntil(0x00));
84 | entry.Fullname = directory + filename;
85 |
86 | if (entry.IsDirectory)
87 | {
88 | entry.Fullname += "/";
89 | currentIndex = BuildFilenames(reader, currentIndex + 1, (int)entry.FileSize, entry.Fullname, stringTableOffset);
90 | }
91 | else
92 | {
93 | ++currentIndex;
94 | }
95 | }
96 |
97 | return currentIndex;
98 | }
99 |
100 | #endregion
101 |
102 | #region Interface Implementations
103 |
104 | public FSTEntry this[int index]
105 | {
106 | get { return fileEntries[index]; }
107 | }
108 |
109 | public IEnumerator GetEnumerator()
110 | {
111 | return ((IEnumerable)fileEntries).GetEnumerator();
112 | }
113 |
114 | IEnumerator IEnumerable.GetEnumerator()
115 | {
116 | return GetEnumerator();
117 | }
118 |
119 | #endregion
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Discs/GC/FSTEntry.cs:
--------------------------------------------------------------------------------
1 | namespace GameFormatReader.GCWii.Discs.GC
2 | {
3 | ///
4 | /// Represents an entry within the FST.
5 | ///
6 | public sealed class FSTEntry
7 | {
8 | #region Public Fields
9 |
10 | ///
11 | /// The size in bytes for each entry in the FST.
12 | ///
13 | public const int EntrySize = 0xC;
14 |
15 | #endregion
16 |
17 | #region Constructor
18 |
19 | internal FSTEntry()
20 | {
21 | }
22 |
23 | #endregion
24 |
25 | #region Properties
26 |
27 | ///
28 | /// Filename offset within the string table.
29 | ///
30 | public uint NameOffset
31 | {
32 | get;
33 | internal set;
34 | }
35 |
36 | ///
37 | /// Actual file offset.
38 | ///
39 | public uint Offset
40 | {
41 | get;
42 | internal set;
43 | }
44 |
45 | ///
46 | /// Length of the file.
47 | ///
48 | ///
49 | /// In the case of the root directory, this will be the total number of entries.
50 | ///
51 | public uint FileSize
52 | {
53 | get;
54 | internal set;
55 | }
56 |
57 | ///
58 | /// The full name of the entry.
59 | ///
60 | public string Fullname
61 | {
62 | get;
63 | internal set;
64 | }
65 |
66 | ///
67 | /// Whether or not this FSTEntry represents a directory.
68 | ///
69 | public bool IsDirectory
70 | {
71 | get { return (NameOffset & 0xFF000000) != 0; }
72 | }
73 |
74 | #endregion
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Discs/Region.cs:
--------------------------------------------------------------------------------
1 | namespace GameFormatReader.GCWii.Discs
2 | {
3 | ///
4 | /// Represents the regions given by region codes.
5 | ///
6 | public enum Region
7 | {
8 | Unknown,
9 | Australia, // 'U'
10 | France, // 'F'
11 | Germany, // 'D'
12 | Italy, // 'I'
13 | Japan, // 'J'
14 | Korea, // 'K'
15 | PAL, // 'P'
16 | Russia, // 'R'
17 | Spanish, // 'S'
18 | Taiwan, // 'T'
19 | USA, // 'E'
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Discs/Wii/DiscHeaderWii.cs:
--------------------------------------------------------------------------------
1 | using GameFormatReader.Common;
2 |
3 | namespace GameFormatReader.GCWii.Discs.Wii
4 | {
5 | ///
6 | /// Represents the of a Wii disc.
7 | ///
8 | public sealed class DiscHeaderWii : DiscHeader
9 | {
10 | #region Constructor
11 |
12 | internal DiscHeaderWii(EndianBinaryReader reader)
13 | {
14 | ReadHeader(reader);
15 | }
16 |
17 | #endregion
18 |
19 | #region Protected Methods
20 |
21 | // Reads the common portion of the header between the Gamecube and Wii discs.
22 | // Disc classes for GC and Wii must implement the rest of the reading.
23 | protected override void ReadHeader(EndianBinaryReader reader)
24 | {
25 | Type = DetermineDiscType(reader.ReadChar());
26 | GameCode = new string(reader.ReadChars(2));
27 | RegionCode = DetermineRegion(reader.ReadChar());
28 | MakerCode = new string(reader.ReadChars(2));
29 | DiscNumber = reader.ReadByte();
30 | AudioStreaming = reader.ReadBoolean();
31 | StreamingBufferSize = reader.ReadByte();
32 |
33 | // Skip unused bytes
34 | reader.BaseStream.Position += 14;
35 |
36 | MagicWord = reader.ReadInt32();
37 |
38 | // Skip the other 4 bytes, since this is a Wii header, not a GameCube one.
39 | reader.BaseStream.Position += 4;
40 |
41 | GameTitle = new string(reader.ReadChars(64));
42 | IsHashVerificationDisabled = reader.ReadBoolean();
43 | IsDiscEncryptionDisabled = reader.ReadBoolean();
44 | }
45 |
46 | #endregion
47 |
48 | #region Properties
49 |
50 | ///
51 | /// Whether or not hash verification is disabled. Will make all disc reads fail even before they reach the DVD drive.
52 | ///
53 | public bool IsHashVerificationDisabled
54 | {
55 | get;
56 | private set;
57 | }
58 |
59 | ///
60 | /// Whether or not disc encryption and h3 hash table loading and verification is disabled.
61 | /// (which effectively also makes all disc reads fail because the h2 hashes won't be able to verify against "something" that will be in the memory of the h3 hash table. none of these two bytes will allow unsigned code)
62 | ///
63 | public bool IsDiscEncryptionDisabled
64 | {
65 | get;
66 | private set;
67 | }
68 |
69 | #endregion
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Discs/Wii/DiscWii.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using GameFormatReader.Common;
3 |
4 | // TODO: Finish this up.
5 |
6 | namespace GameFormatReader.GCWii.Discs.Wii
7 | {
8 | ///
9 | /// Represents a Wii .
10 | ///
11 | public sealed class DiscWii : Disc
12 | {
13 | #region Constructors
14 |
15 | ///
16 | /// Constructor
17 | ///
18 | /// Path to the Wii .
19 | public DiscWii(string filepath) : base(filepath)
20 | {
21 | using (var reader = new EndianBinaryReader(File.OpenRead(filepath), Endian.Big))
22 | {
23 | Header = new DiscHeaderWii(reader);
24 |
25 | // Skip to the partiton data offset.
26 | reader.BaseStream.Position = 0x40000;
27 |
28 | // TODO: Read the rest of the disc.
29 | }
30 | }
31 |
32 | #endregion
33 |
34 | #region Properties
35 |
36 | ///
37 | /// Wii .
38 | ///
39 | public override DiscHeader Header
40 | {
41 | get;
42 | protected set;
43 | }
44 |
45 | #endregion
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Graphics/BNR.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using GameFormatReader.Common;
5 |
6 | namespace GameFormatReader.GCWii.Graphics
7 | {
8 | ///
9 | /// Banner file format
10 | ///
11 | public sealed class BNR
12 | {
13 | #region Constructors
14 |
15 | ///
16 | /// Constructor
17 | ///
18 | /// Path to the BNR file.
19 | public BNR(string filepath)
20 | {
21 | if (filepath == null)
22 | throw new ArgumentNullException(nameof(filepath));
23 |
24 | if (!File.Exists(filepath))
25 | throw new FileNotFoundException($"File {filepath} does not exist.", filepath);
26 |
27 | ReadBNR(filepath);
28 | }
29 |
30 | ///
31 | /// Constructor
32 | ///
33 | /// Data that contains the BNR file.
34 | /// Offset in the data to begin reading at.
35 | public BNR(byte[] data, int offset)
36 | {
37 | if (data == null)
38 | throw new ArgumentNullException(nameof(data));
39 |
40 | if (offset < 0)
41 | throw new ArgumentOutOfRangeException(nameof(offset), $"{nameof(offset)} cannot be less than zero.");
42 |
43 | if (offset >= data.Length)
44 | throw new ArgumentOutOfRangeException(nameof(offset), $"{nameof(offset)} cannot be greater than the given data's size.");
45 |
46 | ReadBNR(data, offset);
47 | }
48 |
49 | #endregion
50 |
51 | #region Enums
52 |
53 | ///
54 | /// Banner types
55 | ///
56 | public enum BNRType
57 | {
58 | ///
59 | /// Banner type 1 (US/JP games)
60 | ///
61 | BNR1,
62 |
63 | ///
64 | /// Banner type 2 (EU games)
65 | ///
66 | BNR2,
67 | }
68 |
69 | #endregion
70 |
71 | #region Public Properties
72 |
73 | ///
74 | /// Banner type
75 | /// BNR1 for (US/JP)
76 | /// BNR2 for EU.
77 | ///
78 | public BNRType BannerType
79 | {
80 | get;
81 | private set;
82 | }
83 |
84 | ///
85 | /// Graphical data stored in the banner.
86 | /// Pixel format is RGB5A3.
87 | ///
88 | public byte[] Data
89 | {
90 | get;
91 | private set;
92 | }
93 |
94 | ///
95 | /// Basic game title.
96 | ///
97 | public string GameTitle
98 | {
99 | get;
100 | private set;
101 | }
102 |
103 | ///
104 | /// Name of the company/developer of the game.
105 | ///
106 | public string DeveloperName
107 | {
108 | get;
109 | private set;
110 | }
111 |
112 | ///
113 | /// Full title of the game.
114 | ///
115 | public string FullGameTitle
116 | {
117 | get;
118 | private set;
119 | }
120 |
121 | ///
122 | /// Full name of the company/developer or description.
123 | ///
124 | public string FullDeveloperName
125 | {
126 | get;
127 | private set;
128 | }
129 |
130 | ///
131 | /// Descriptions of the game that this banner is for.
132 | ///
133 | ///
134 | /// In BNR1 type banners, this will simply contain one description.
135 | /// In BNR2 type banners, this will contain six
136 | /// different language versions of the description.
137 | /// However, some may simply be the same as the English description,
138 | /// it depends on the game for the most part.
139 | ///
140 | public string[] GameDescriptions
141 | {
142 | get;
143 | private set;
144 | }
145 |
146 | #endregion
147 |
148 | #region Private Methods
149 |
150 | // File-based reading
151 | private void ReadBNR(string filepath)
152 | {
153 | // Same thing as buffer-based reading
154 | // only difference is we just start at offset zero.
155 | ReadBNR(File.ReadAllBytes(filepath), 0);
156 | }
157 |
158 | // Buffer-based reading
159 | private void ReadBNR(byte[] data, int offset)
160 | {
161 | var ms = new MemoryStream(data, offset, data.Length);
162 |
163 | using (var reader = new EndianBinaryReader(ms, Endian.Big))
164 | {
165 | string magicWord = new string(reader.ReadChars(4));
166 | BannerType = (magicWord == "BNR1") ? BNRType.BNR1 : BNRType.BNR2;
167 |
168 | // Skip padding bytes
169 | reader.BaseStream.Position = 0x20;
170 |
171 | Data = reader.ReadBytes(0x1800);
172 |
173 | // Name related data
174 | Encoding shiftJis = Encoding.GetEncoding("shift_jis");
175 | GameTitle = shiftJis.GetString(reader.ReadBytes(0x20));
176 | DeveloperName = shiftJis.GetString(reader.ReadBytes(0x20));
177 | FullGameTitle = shiftJis.GetString(reader.ReadBytes(0x40));
178 | FullDeveloperName = shiftJis.GetString(reader.ReadBytes(0x40));
179 |
180 | if (BannerType == BNRType.BNR1)
181 | {
182 | GameDescriptions = new string[1];
183 | GameDescriptions[0] = shiftJis.GetString(reader.ReadBytes(0x80));
184 | }
185 | else if (BannerType == BNRType.BNR2)
186 | {
187 | GameDescriptions = new string[6];
188 |
189 | for (int i = 0; i < 6; i++)
190 | {
191 | GameDescriptions[i] = shiftJis.GetString(reader.ReadBytes(0x80));
192 | }
193 | }
194 | }
195 | }
196 |
197 | #endregion
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Graphics/Enums/TLUTFormat.cs:
--------------------------------------------------------------------------------
1 | namespace GameFormatReader.GCWii.Graphics.Enums
2 | {
3 | ///
4 | /// Defines possible texture lookup table formats.
5 | ///
6 | public enum TLUTFormat
7 | {
8 | /// 8-bit intensity with 8-bit alpha, 8x8 tiles
9 | IA8 = 0,
10 | /// 4x4 tiles
11 | RGB565 = 1,
12 | /// 4x4 tiles - is RGB5 if color value is negative and RGB4A3 otherwise.
13 | RGB5A3 = 2,
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Graphics/Enums/TextureFilter.cs:
--------------------------------------------------------------------------------
1 | namespace GameFormatReader.GCWii.Graphics.Enums
2 | {
3 | ///
4 | /// Represents possible texture filtering modes on the GameCube and Wii.
5 | ///
6 | public enum TextureFilter
7 | {
8 | ///
9 | /// Filters using the texture element that is nearest (in Manhattan distance)
10 | /// to the center of the pixel being textured.
11 | ///
12 | Nearest = 0,
13 |
14 | ///
15 | /// Filters using the weighted average of the four texture elements
16 | /// that are closest to the center of the pixel being textured.
17 | ///
18 | Linear = 1,
19 |
20 | ///
21 | /// Chooses the mipmap that most closely matches the size of the pixel
22 | /// being textured and uses the criterion
23 | /// (the texture element nearest to the center of the pixel)
24 | /// to produce a texture value.
25 | ///
26 | NearestMipmap = 2,
27 |
28 | ///
29 | /// Chooses the mipmap that most closely matches the size of the pixel
30 | /// being textured and uses the criterion
31 | /// (a weighted average of the four texture elements that are closest
32 | /// to the center of the pixel) to produce a texture value.
33 | ///
34 | LinearMipmapNearest = 3,
35 |
36 | ///
37 | /// Chooses the two mipmaps that most closely match the size of the pixel
38 | /// being textured and uses the criterion
39 | /// (the texture element nearest to the center of the pixel)
40 | /// to produce a texture value from each mipmap.
41 | /// The final texture value is a weighted average of those two values.
42 | ///
43 | NearestMipmapLinear = 4,
44 |
45 | ///
46 | /// Chooses the two mipmaps that most closely match the size of the pixel
47 | /// being textured and uses the criterion
48 | /// (a weighted average of the four texture elements that are closest to the center of the pixel)
49 | /// to produce a texture value from each mipmap.
50 | /// The final texture value is a weighted average of those two values.
51 | ///
52 | LinearMipmapLinear = 5,
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Graphics/Enums/TextureFormat.cs:
--------------------------------------------------------------------------------
1 | namespace GameFormatReader.GCWii.Graphics.Enums
2 | {
3 | ///
4 | /// Defines possible texture formats on the GameCube and Wii.
5 | ///
6 | public enum TextureFormat
7 | {
8 | /// 4-bit intensity, 8x8 tiles
9 | I4 = 0,
10 | /// 8-bit intensity, 8x4 tiles
11 | I8 = 1,
12 | /// 4-bit intensity with 4-bit alpha, 8x4 tiles
13 | IA4 = 2,
14 | /// 8-bit intensity with 8-bit alpha, 8x8 tiles
15 | IA8 = 3,
16 | /// 4x4 tiles
17 | RGB565 = 4,
18 | /// 4x4 tiles - is RGB5 if color value is negative and RGB4A3 otherwise.
19 | RGB5A3 = 5,
20 | /// 4x4 tiles in two cache lines - first is AR and second is GB
21 | RGBA8 = 6,
22 | /// 4-bit color index, 8x8 tiles
23 | CI4 = 8,
24 | /// 8-bit color index, 8x4 tiles
25 | CI8 = 9,
26 | /// 14-bit color index, 4x4 tiles
27 | CI14X2 = 10,
28 | /// S3TC compressed, 2x2 blocks of 4x4 tiles
29 | CMP = 14,
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Graphics/Enums/WrapMode.cs:
--------------------------------------------------------------------------------
1 | namespace GameFormatReader.GCWii.Graphics.Enums
2 | {
3 | ///
4 | /// Defines possible texture wrap modes
5 | ///
6 | public enum WrapMode
7 | {
8 | ///
9 | /// The coordinate will simply be clamped between 0 and 1.
10 | ///
11 | Clamp = 0,
12 |
13 | ///
14 | /// Integer part of the coordinate will be ignored and a repeating pattern is formed.
15 | ///
16 | Repeat = 1,
17 |
18 | ///
19 | /// Integer part of the coordinate will be ignored, but it
20 | /// will be mirrored when the integer part of the coordinate is odd.
21 | ///
22 | Mirror = 2,
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Graphics/TPL.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using GameFormatReader.Common;
6 | using GameFormatReader.GCWii.Graphics.Enums;
7 |
8 | namespace GameFormatReader.GCWii.Graphics
9 | {
10 | ///
11 | /// Represents a custom format that stores
12 | /// texture and pallet information.
13 | ///
14 | public sealed class TPL : IEnumerable
15 | {
16 | #region Private Fields
17 |
18 | private const int CorrectHeaderSize = 0x0C;
19 |
20 | #endregion
21 |
22 | #region Constructors
23 |
24 | ///
25 | /// Constructor
26 | ///
27 | /// Path to the TPL file.
28 | public TPL(string filepath)
29 | {
30 | if (filepath == null)
31 | throw new ArgumentNullException(nameof(filepath));
32 |
33 | if (!File.Exists(filepath))
34 | throw new FileNotFoundException($"File {filepath} does not exist.", filepath);
35 |
36 | using (var reader = new EndianBinaryReader(File.OpenRead(filepath), Endian.Big))
37 | {
38 | ReadHeader(reader);
39 | ReadTextures(reader);
40 | }
41 | }
42 |
43 | ///
44 | /// Constructor
45 | ///
46 | /// Byte array containing TPL data.
47 | public TPL(byte[] data)
48 | {
49 | if (data == null)
50 | throw new ArgumentNullException(nameof(data));
51 |
52 | using (var reader = new EndianBinaryReader(new MemoryStream(data), Endian.Big))
53 | {
54 | ReadHeader(reader);
55 | ReadTextures(reader);
56 | }
57 | }
58 |
59 | #endregion
60 |
61 | #region Structs
62 |
63 | ///
64 | /// Represents an embedded texture
65 | ///
66 | public sealed class Texture
67 | {
68 | /// Height of this texture in pixels.
69 | public int Height { get; internal set; }
70 | /// Width of this texture in pixels.
71 | public int Width { get; internal set; }
72 | /// Texture format
73 | public TextureFormat TexFormat { get; internal set; }
74 | /// Texture data
75 | public byte[] TextureData { get; internal set; }
76 | /// Horizontal wrapping mode
77 | public WrapMode WrapS { get; internal set; }
78 | /// Vertical wrapping mode
79 | public WrapMode WrapT { get; internal set; }
80 | /// Minimization filter
81 | public TextureFilter MinFilter { get; internal set; }
82 | /// Magnification filter
83 | public TextureFilter MagFilter { get; internal set; }
84 | /// LOD (Level of Detail) bias.
85 | public float LODBias { get; internal set; }
86 | /// Edge level of detail
87 | public byte EdgeLOD { get; internal set; }
88 | /// Minimum level of detail
89 | public byte MinLOD { get; internal set; }
90 | /// Maximum level of detail
91 | public byte MaxLOD { get; internal set; }
92 | /// Whether or not data is unpacked
93 | public byte IsUnpacked { get; internal set; }
94 | /// This texture's palette (optional).
95 | public Palette Palette { get; internal set; }
96 | }
97 |
98 | ///
99 | /// Represents an embedded palette
100 | ///
101 | public sealed class Palette
102 | {
103 | /// Number of palettes
104 | public short NumItems { get; internal set; }
105 | /// Whether or not data is unpacked.
106 | public byte IsUnpacked { get; internal set; }
107 | /// Indicates padding? Truthfully, I don't know
108 | public byte Padding { get; internal set; }
109 | /// Palette format
110 | public TLUTFormat PaletteFormat { get; internal set; }
111 | /// Palette data
112 | public byte[] Data { get; internal set; }
113 | }
114 |
115 | #endregion
116 |
117 | #region Properties
118 |
119 | ///
120 | /// Textures within this file.
121 | ///
122 | public Texture[] Textures
123 | {
124 | get;
125 | internal set;
126 | }
127 |
128 | #endregion
129 |
130 | #region Private Methods
131 |
132 | private void ReadHeader(EndianBinaryReader reader)
133 | {
134 | byte[] magic = reader.ReadBytes(4);
135 | if (magic[0] != 0x00 || magic[1] != 0x20 ||
136 | magic[2] != 0xAF || magic[3] != 0x30)
137 | {
138 | throw new IOException("Not a valid TPL file - Incorrect file magic");
139 | }
140 |
141 | int numTextures = reader.ReadInt32();
142 | Textures = new Texture[numTextures];
143 |
144 | // Note that the header size must be 0x0C to be valid.
145 | int headerSize = reader.ReadInt32();
146 |
147 | if (headerSize != CorrectHeaderSize)
148 | throw new IOException($"Incorrect TPL header size. Should be {CorrectHeaderSize}, was {headerSize}");
149 | }
150 |
151 | private void ReadTextures(EndianBinaryReader reader)
152 | {
153 | for (int i = 0; i < Textures.Length; i++)
154 | {
155 | int textureOffset = reader.ReadInt32();
156 | int paletteOffset = reader.ReadInt32();
157 |
158 | Textures[i] = new Texture();
159 | Textures[i].Height = reader.ReadInt16At(textureOffset + 0x00);
160 | Textures[i].Width = reader.ReadInt16At(textureOffset + 0x02);
161 | Textures[i].TexFormat = (TextureFormat) reader.ReadInt32At(textureOffset + 0x04);
162 |
163 | int texDataOffset = reader.ReadInt32At(textureOffset + 0x08);
164 | Textures[i].TextureData = reader.ReadBytesAt(texDataOffset, GetTextureDataSize(i));
165 |
166 | Textures[i].WrapS = (WrapMode) reader.ReadInt32At(textureOffset + 0x0C);
167 | Textures[i].WrapT = (WrapMode) reader.ReadInt32At(textureOffset + 0x10);
168 |
169 | Textures[i].MinFilter = (TextureFilter) reader.ReadInt32At(textureOffset + 0x14);
170 | Textures[i].MagFilter = (TextureFilter) reader.ReadInt32At(textureOffset + 0x18);
171 | Textures[i].LODBias = reader.ReadSingleAt(textureOffset + 0x1C);
172 | Textures[i].EdgeLOD = reader.ReadByteAt(textureOffset + 0x20);
173 | Textures[i].MinLOD = reader.ReadByteAt(textureOffset + 0x21);
174 | Textures[i].MaxLOD = reader.ReadByteAt(textureOffset + 0x22);
175 | Textures[i].IsUnpacked = reader.ReadByteAt(textureOffset + 0x23);
176 |
177 | if (paletteOffset != 0)
178 | {
179 | Textures[i].Palette = new Palette();
180 | Textures[i].Palette.NumItems = reader.ReadInt16At(paletteOffset + 0x00);
181 | Textures[i].Palette.IsUnpacked = reader.ReadByteAt(paletteOffset + 0x02);
182 | Textures[i].Palette.Padding = reader.ReadByteAt(paletteOffset + 0x03);
183 | Textures[i].Palette.PaletteFormat = (TLUTFormat) reader.ReadInt32At(paletteOffset + 0x04);
184 |
185 | int dataOffset = reader.ReadInt32At(paletteOffset + 0x08);
186 | Textures[i].Palette.Data = reader.ReadBytesAt(dataOffset, Textures[i].Palette.NumItems);
187 | }
188 | else
189 | {
190 | Textures[i].Palette = null;
191 | }
192 | }
193 | }
194 |
195 | private int GetTextureDataSize(int i)
196 | {
197 | if (i < 0)
198 | throw new ArgumentException($"{nameof(i)} cannot be less than zero", nameof(i));
199 |
200 | if (i >= Textures.Length)
201 | throw new ArgumentException($"{nameof(i)} cannot be larger than the total number of textures", nameof(i));
202 |
203 | int size = 0;
204 |
205 | int width = Textures[i].Width;
206 | int height = Textures[i].Height;
207 |
208 | switch (Textures[i].TexFormat)
209 | {
210 | case TextureFormat.I4:
211 | case TextureFormat.CI4:
212 | case TextureFormat.CMP:
213 | size = ((width + 7) >> 3)*((height + 7) >> 3)*32;
214 | break;
215 |
216 | case TextureFormat.I8:
217 | case TextureFormat.IA4:
218 | case TextureFormat.CI8:
219 | size = ((width + 7) >> 3)*((height + 7) >> 2)*32;
220 | break;
221 |
222 | case TextureFormat.IA8:
223 | case TextureFormat.CI14X2:
224 | case TextureFormat.RGB565:
225 | case TextureFormat.RGB5A3:
226 | size = ((width + 3) >> 2)*((height + 3) >> 2)*32;
227 | break;
228 |
229 | case TextureFormat.RGBA8:
230 | size = ((width + 3) >> 2)*((height + 3) >> 2)*64;
231 | break;
232 | }
233 |
234 | return size;
235 | }
236 |
237 | #endregion
238 |
239 | #region Interface Methods and Properties
240 |
241 | ///
242 | /// Retrieves the specified by the given index.
243 | ///
244 | /// The index to get the at.
245 | /// The at the given index.
246 | public Texture this[int index]
247 | {
248 | get { return Textures[index]; }
249 | }
250 |
251 | public IEnumerator GetEnumerator()
252 | {
253 | return ((IEnumerable)Textures).GetEnumerator();
254 | }
255 |
256 | IEnumerator IEnumerable.GetEnumerator()
257 | {
258 | return GetEnumerator();
259 | }
260 |
261 | #endregion
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/GameFormatReader/GCWii/Sound/BRSTM.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using GameFormatReader.Common;
4 |
5 | namespace GameFormatReader.GCWii.Sound
6 | {
7 | ///
8 | /// A somewhat common sound format used within some Wii games, such as
9 | /// Super Smash Bros. Brawl and Mario Kart Wii which contains ADPCM sound data.
10 | ///
11 | public sealed class BRSTM
12 | {
13 | #region Constructor
14 |
15 | ///
16 | /// Constructor
17 | ///
18 | /// Path to the BRSTM file to load.
19 | public BRSTM(string filename) : this(File.ReadAllBytes(filename))
20 | {
21 | }
22 |
23 | ///
24 | /// Constructor
25 | ///
26 | /// Byte array containing BRSTM data.
27 | public BRSTM(byte[] data)
28 | {
29 | if (data == null)
30 | throw new ArgumentNullException(nameof(data));
31 |
32 | BinaryReader br = new BinaryReader(new MemoryStream(data));
33 |
34 | char[] headerID = br.ReadChars(4);
35 |
36 | if (!IsValidHeader(headerID))
37 | throw new ArgumentException("Cannot read BRSTM file. Incorrect header ID", nameof(data));
38 |
39 | Header = new FileHeader();
40 | Header.ID = new string(headerID);
41 | Header.ByteOrderMark = br.ReadBytes(2);
42 |
43 | Endian endianness = IsLittleEndian(Header.ByteOrderMark) ? Endian.Little : Endian.Big;
44 | using (var reader = new EndianBinaryReader(br.BaseStream, endianness))
45 | {
46 | Header.MajorVersion = reader.ReadByte();
47 | Header.MinorVersion = reader.ReadByte();
48 | Header.FileSize = reader.ReadInt32();
49 | Header.HeaderSize = reader.ReadUInt16();
50 | Header.NumberOfChunks = reader.ReadUInt16();
51 | Header.OffsetToHeadChunk = reader.ReadInt32();
52 | Header.HeadChunkSize = reader.ReadInt32();
53 | Header.OffsetToAdpcChunk = reader.ReadInt32();
54 | Header.AdpcChunkSize = reader.ReadInt32();
55 | Header.OffsetToDataChunk = reader.ReadInt32();
56 | Header.DataChunkSize = reader.ReadInt32();
57 | Header.Unknown = reader.ReadBytes(24);
58 |
59 | ReadHeadChunk(reader);
60 | ReadAdpcChunk(reader);
61 | ReadDataChunk(reader);
62 | }
63 | }
64 |
65 | #endregion
66 |
67 | #region Section Classes
68 |
69 | ///
70 | /// BRSTM file header
71 | ///
72 | public sealed class FileHeader
73 | {
74 | internal FileHeader()
75 | {
76 | }
77 |
78 | ///
79 | /// Header ID
80 | ///
81 | public string ID { get; internal set; }
82 |
83 | ///
84 | /// Byte order mark. Usually 0xFE 0xFF for big endian.
85 | ///
86 | public byte[] ByteOrderMark { get; internal set; }
87 |
88 | ///
89 | /// Major version
90 | ///
91 | public byte MajorVersion { get; internal set; }
92 |
93 | ///
94 | /// Minor version
95 | ///
96 | public byte MinorVersion { get; internal set; }
97 |
98 | ///
99 | /// Size of the whole file.
100 | ///
101 | public int FileSize { get; internal set; }
102 |
103 | ///
104 | /// Size of the file header.
105 | ///
106 | public int HeaderSize { get; internal set; }
107 |
108 | ///
109 | /// Number of chunks.
110 | ///
111 | public int NumberOfChunks { get; internal set; }
112 |
113 | ///
114 | /// Offset to HEAD chunk
115 | ///
116 | public int OffsetToHeadChunk { get; internal set; }
117 |
118 | ///
119 | /// HEAD chunk size.
120 | ///
121 | public int HeadChunkSize { get; internal set; }
122 |
123 | ///
124 | /// Offset to the ADPC chunk.
125 | ///
126 | public int OffsetToAdpcChunk { get; internal set; }
127 |
128 | ///
129 | /// ADPC chunk size.
130 | ///
131 | public int AdpcChunkSize { get; internal set; }
132 |
133 | ///
134 | /// Offset to the DATA chunk.
135 | ///
136 | public int OffsetToDataChunk { get; internal set; }
137 |
138 | ///
139 | /// DATA chunk size
140 | ///
141 | public int DataChunkSize { get; internal set; }
142 |
143 | ///
144 | /// Unknown data. Suspected to be padding.
145 | ///
146 | public byte[] Unknown { get; internal set; }
147 | }
148 |
149 | ///
150 | /// HEAD section
151 | ///
152 | public sealed class HeadSection
153 | {
154 | internal HeadSection()
155 | {
156 | }
157 |
158 | /// Section header - "HEAD"
159 | public string ID { get; internal set; }
160 |
161 | /// Section length in bytes
162 | public int Length { get; internal set; }
163 |
164 | /// Section data
165 | public byte[] Data { get; internal set; }
166 | }
167 |
168 | ///
169 | /// ADPC section
170 | ///
171 | public sealed class AdpcSection
172 | {
173 | internal AdpcSection()
174 | {
175 | }
176 |
177 | /// Section header - "ADPC"
178 | public string ID { get; internal set; }
179 |
180 | /// Section length in bytes.
181 | public int Length { get; internal set; }
182 |
183 | /// Section data
184 | public byte[] Data { get; internal set; }
185 | }
186 |
187 | ///
188 | /// DATA section
189 | ///
190 | public sealed class DataSection
191 | {
192 | internal DataSection()
193 | {
194 | }
195 |
196 | /// Section header - "DATA"
197 | public string ID { get; internal set; }
198 |
199 | /// Section length in bytes
200 | public int Length { get; internal set; }
201 |
202 | /// Unknown. Suspected to be number of sub-section in ADPCM data.
203 | public int Unknown { get; internal set; }
204 |
205 | /// Padding/Null bytes
206 | public byte[] Padding { get; internal set; }
207 |
208 | /// ADPCM Data
209 | public byte[] Data { get; internal set; }
210 |
211 | /// More padding bytes
212 | public byte[] Padding2 { get; internal set; }
213 | }
214 |
215 | #endregion
216 |
217 | #region Properties
218 |
219 | ///
220 | /// File header
221 | ///
222 | public FileHeader Header
223 | {
224 | get;
225 | private set;
226 | }
227 |
228 | ///
229 | /// HEAD section
230 | ///
231 | public HeadSection HEAD
232 | {
233 | get;
234 | private set;
235 | }
236 |
237 | ///
238 | /// ADPC section
239 | ///
240 | public AdpcSection ADPC
241 | {
242 | get;
243 | private set;
244 | }
245 |
246 | ///
247 | /// DATA section
248 | ///
249 | public DataSection DATA
250 | {
251 | get;
252 | private set;
253 | }
254 |
255 | #endregion
256 |
257 | #region Private Methods
258 |
259 | private void ReadHeadChunk(EndianBinaryReader reader)
260 | {
261 | reader.BaseStream.Position = Header.OffsetToHeadChunk;
262 |
263 | HEAD = new HeadSection();
264 | HEAD.ID = new string(reader.ReadChars(4));
265 | HEAD.Length = reader.ReadInt32();
266 | HEAD.Data = reader.ReadBytes(HEAD.Length - 8); // -8 to take reading the ID and length into account.
267 | }
268 |
269 | private void ReadAdpcChunk(EndianBinaryReader reader)
270 | {
271 | reader.BaseStream.Position = Header.OffsetToAdpcChunk;
272 |
273 | ADPC = new AdpcSection();
274 | ADPC.ID = new string(reader.ReadChars(4));
275 | ADPC.Length = reader.ReadInt32();
276 | ADPC.Data = reader.ReadBytes(ADPC.Length - 8); // - 8 to take reading the ID and length into account.
277 | }
278 |
279 | private void ReadDataChunk(EndianBinaryReader reader)
280 | {
281 | reader.BaseStream.Position = Header.OffsetToDataChunk;
282 |
283 | DATA = new DataSection();
284 | DATA.ID = new string(reader.ReadChars(4));
285 | DATA.Length = reader.ReadInt32();
286 | DATA.Unknown = reader.ReadInt32();
287 | DATA.Padding = reader.ReadBytes(14);
288 | DATA.Data = reader.ReadBytes(DATA.Length - 26); // - 26 to take into account the previously read data
289 | DATA.Padding2 = reader.ReadBytes(26);
290 | }
291 |
292 | #endregion
293 |
294 | #region Static Helper Methods
295 |
296 | // Checks if the file contains a valid header ID.
297 | private static bool IsValidHeader(char[] data)
298 | {
299 | if (data == null)
300 | throw new ArgumentNullException(nameof(data));
301 |
302 | if (data.Length < 4)
303 | throw new ArgumentException("Invalid header magic, data too small.", nameof(data));
304 |
305 | return data[0] == 'R' &&
306 | data[1] == 'S' &&
307 | data[2] == 'T' &&
308 | data[3] == 'M';
309 | }
310 |
311 | private static bool IsLittleEndian(byte[] data)
312 | {
313 | if (data == null)
314 | throw new ArgumentNullException(nameof(data));
315 |
316 | if (data.Length < 2)
317 | throw new ArgumentException("Byte order mark data cannot be less than 2 bytes in size.", nameof(data));
318 |
319 | return data[0] == 0xFF &&
320 | data[1] == 0xFE;
321 | }
322 |
323 | #endregion
324 | }
325 | }
326 |
--------------------------------------------------------------------------------
/GameFormatReader/GameFormatReader.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}
8 | Library
9 | Properties
10 | GameFormatReader
11 | GameFormatReader
12 | v4.6
13 | 512
14 |
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
75 |
--------------------------------------------------------------------------------
/GameFormatReader/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("GameFormatReader")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("GameFormatReader")]
13 | [assembly: AssemblyCopyright("")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("0d7daa9b-5943-442c-a8e8-ddc420967d9a")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | #GameFormatReader
2 |
3 | A reader for various game formats.
4 |
5 | ## License
6 |
7 | WTFPL - Go nuts.
--------------------------------------------------------------------------------