├── 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. --------------------------------------------------------------------------------