/// <summary> /// Reads a project XML file and relinks all the parts for file rebuilding /// </summary> /// <param name="filePath">XML file to parse</param> public BNSAXMLFile(string filePath) { this.FilePath = filePath; Console.WriteLine("Loading project file: " + filePath); XmlDocument doc = new XmlDocument(); doc.Load(filePath); //Parse Process: //1. Read animation tree //2. Read tileset folder //3. Read minianim folder //4. Read palette folder //5. Read oamdatalists folder //6. Link all objects //Build lists, mark pointer placeholders //Byte align after lists are built //insert pointers //merge into single file string tilesetBasepath = Directory.GetParent(filePath).FullName + "\\tilesets\\tileset"; string palettesBasepath = Directory.GetParent(filePath).FullName + "\\palettes\\palette"; string oamDataListsBasepath = Directory.GetParent(filePath).FullName + "\\oamdatalists\\oamdatalist"; string miniAnimsBasepath = Directory.GetParent(filePath).FullName + "\\minianims\\minianim"; XmlNodeList animationNodes = doc.DocumentElement.SelectNodes("/bnsafile/animations/animation"); Console.WriteLine("Found " + animationNodes.Count + " animations"); Animations = new List <Animation>(); foreach (XmlNode animationNode in animationNodes) { //Read Animation XML Animation animation = new Animation(animationNode); //Verify references exist in filesystem foreach (Frame f in animation.Frames) { //Verify Tileset string tilesetPath = tilesetBasepath + f.TilesetIndex.ToString().PadLeft(3, '0') + ".bin"; if (!File.Exists(tilesetPath)) { IsValid = false; Console.WriteLine("Tileset file missing: " + tilesetPath + ", referenced by Anim " + animation.Index + " frame " + f.Index); return; } //Verify OAM string oamPath = oamDataListsBasepath + f.OAMDataListIndex + "-0-0.bin"; if (!File.Exists(oamPath)) { IsValid = false; Console.WriteLine("OAM Data List file missing: " + oamPath + ", referenced by Anim " + animation.Index + " frame " + f.Index); return; } //Verify MiniAnim string minianimFrame1 = miniAnimsBasepath + f.MiniAnimationIndex + "-0-0.bin"; if (!File.Exists(minianimFrame1)) { IsValid = false; Console.WriteLine("MiniAnimation file missing: " + minianimFrame1 + ", referenced by Anim " + animation.Index + " frame " + f.Index); return; } } Animations.Add(animation); Console.WriteLine("OK - animation contains all direct binary references"); } //Read Tilesets Tilesets = new List <Tileset>(); int index = 0; for (int i = 0; i < 999; i++) { string tilesetPath = tilesetBasepath + index.ToString().PadLeft(3, '0') + ".bin"; if (File.Exists(tilesetPath)) { Tileset tileset = new Tileset(tilesetPath, i); Tilesets.Add(tileset); } else { break; //No more tilesets } index++; } Console.WriteLine("Read " + Tilesets.Count + " tilesets"); //Read Palettes Palettes = new List <Palette>(); for (int i = 0; i < 16; i++) { string palettePath = palettesBasepath + i.ToString().PadLeft(2, '0') + ".bin"; if (File.Exists(palettePath)) { Palette palette = new Palette(palettePath); Palettes.Add(palette); } else { break; //No more palettes } } Console.WriteLine("Read " + Palettes.Count + " palettes"); //Read MiniAnims string[] miniAnimFiles = Directory.GetFiles(Directory.GetParent(filePath).FullName + "\\minianims"); int maxindex = 0; foreach (string file in miniAnimFiles) { string fname = Path.GetFileNameWithoutExtension(file); fname = fname.Substring(8); int dashIndex = fname.IndexOf('-'); fname = fname.Substring(0, dashIndex); maxindex = Math.Max(maxindex, Int32.Parse(fname)); } Console.WriteLine("Found " + (maxindex + 1) + " mini animations"); MiniAnimationGroups = new List <MiniAnimGroup>(); for (index = 0; index <= maxindex; index++) { int subindex = 0; Console.WriteLine("Parsing Group " + index); MiniAnimGroup group = new MiniAnimGroup(index); while (File.Exists(miniAnimsBasepath + maxindex + "-" + subindex + "-0.bin")) { //MiniAnimation List MiniAnim animation = new MiniAnim(miniAnimsBasepath, index, subindex); group.MiniAnimations.Add(animation); subindex++; } MiniAnimationGroups.Add(group); } //Read OAM Data string[] oamListEntries = Directory.GetFiles(Directory.GetParent(filePath).FullName + "\\oamdatalists"); maxindex = 0; foreach (string file in oamListEntries) { string fname = Path.GetFileNameWithoutExtension(file); fname = fname.Substring(11); int dashIndex = fname.IndexOf('-'); fname = fname.Substring(0, dashIndex); maxindex = Math.Max(maxindex, Int32.Parse(fname)); } Console.WriteLine("Found " + (maxindex + 1) + " OAM Data List Groups"); OAMDataListGroups = new List <OAMDataListGroup>(); for (int i = 0; i <= maxindex; i++) { Console.WriteLine("Parsing OAM Data List Group " + i); OAMDataListGroup list = new OAMDataListGroup(oamDataListsBasepath, i); OAMDataListGroups.Add(list); } IsValid = true; }
private void loadBNSA(Stream bnsaStream) { //HEADER LargestTileset = bnsaStream.ReadByte(); //Magic Number Test if (bnsaStream.ReadByte() != 0 || bnsaStream.ReadByte() != 1) //0x1 and 0x2 positions { //Invalid Magic Number ValidBNSA = false; return; } AnimationCount = bnsaStream.ReadByte(); Console.WriteLine("Number of Animations: " + AnimationCount); //Read Animation Pointers for (int i = 0; i < AnimationCount; i++) { int animationPointer = ReadIntegerFromStream(bnsaStream); long nextPosition = bnsaStream.Position; Animation animation = new Animation(this, animationPointer, bnsaStream); Animations.Add(animation); if (i < AnimationCount - 1) { bnsaStream.Seek(nextPosition, SeekOrigin.Begin); //reset position to next pointer } } //Read Tilesets TilesetStartPointer = bnsaStream.Position; Console.WriteLine("Reading Tilesets, starting at 0x" + TilesetStartPointer.ToString("X2")); int index = 0; while (bnsaStream.Position < ProbablyPaletteStartPointer) { //Read Tilesets Tileset ts = new Tileset(bnsaStream, index); Tilesets.Add(ts); //Might need some extra checking... index++; } //Read Palettes PaletteStartPointer = bnsaStream.Position; Console.WriteLine("Found start of Palettes at 0x" + PaletteStartPointer.ToString("X2")); if (ReadIntegerFromStream(bnsaStream) == 0x20) { while (true) { long pos = bnsaStream.Position; //Verify next item is a palette and not a minianim int minianimCheckPtr1 = ReadIntegerFromStream(bnsaStream); if (minianimCheckPtr1 % 4 == 0) { //All pointers in the minianim table are lined up on a 4-byte boundary. //Indexing starts at 0. Each pointer is 4 bytes, so the first one will have to point to a 4-point value //could be an pointer to 1-frame minianim, maybe... if (minianimCheckPtr1 == 4) { long subpos = bnsaStream.Position; //Check for 1 pointer minianim signature if (bnsaStream.ReadByte() == 0x0 && bnsaStream.ReadByte() == 0x01 && bnsaStream.ReadByte() == 0x80) { bnsaStream.Seek(pos, SeekOrigin.Begin); break; //Not a palette. Next Item is a single-frame minianim group. End of Palette Block } else { bnsaStream.Seek(subpos, SeekOrigin.Begin); //rewind and continue check } } //Could be a multi-miniframe mugshot, let's check the amount of pointers and the values. Boolean determinedDataIsPalette = false; int numberOfMiniAnimsInGroup = minianimCheckPtr1 / 4; //Pointer will go to first value after pointer table, so divide by num bytes in a pointer if (minianimCheckPtr1 == 0 || numberOfMiniAnimsInGroup * 4 > bnsaStream.Length - pos) { //couldn't possbibly be a minianim. Read it as a palette. determinedDataIsPalette = true; } if (!determinedDataIsPalette) { int previousPointerToCheckAgainst = minianimCheckPtr1; Boolean validMiniAnimPointerData = true; for (int i = 1; i < numberOfMiniAnimsInGroup; i++) { int nextMiniAnimCheckPtr = ReadIntegerFromStream(bnsaStream); if (nextMiniAnimCheckPtr < previousPointerToCheckAgainst) { validMiniAnimPointerData = false; break; } } if (validMiniAnimPointerData) { //Probably a minianim bnsaStream.Seek(pos, SeekOrigin.Begin); break; } } //end of check for readAsPalette } bnsaStream.Seek(pos, SeekOrigin.Begin); Console.WriteLine("Reading Palette 0x" + bnsaStream.Position.ToString("X2")); Palette palette = new Palette(bnsaStream); OriginalPalettes.Add(palette); //Console.WriteLine("Reading next Palette 0x" + bnsaStream.Position.ToString("X2")); } }//} else //{ // Console.WriteLine("Palettes should start here, but we didn't find 0x00000020! (At position 0x" + PaletteStartPointer.ToString("X2") + ")"); // return; //} //Read Mini-Animations Console.WriteLine("Reading MiniAnim Data at 0x" + bnsaStream.Position.ToString("X2")); while (true) { //Validate next data is a mini animation. long groupStartPos = bnsaStream.Position; MiniAnimGroup group = new MiniAnimGroup(bnsaStream); if (!group.IsValid) { //End of Mini-Anims bnsaStream.Seek(groupStartPos, SeekOrigin.Begin); break; } MiniAnimGroups.Add(group); //Round up to the next 4 byte boundary bnsaStream.ReadByte(); //pos++ while (bnsaStream.Position % 4 != 0) { bnsaStream.ReadByte(); //Official game padding. Since we are assuming these are all official, when repacking we should also follow this padding rule. } } //Read OAM Data Block Lists Console.WriteLine("Reading OAM Data Blocks at 0x" + bnsaStream.Position.ToString("X2")); int oindex = 0; while (bnsaStream.Position < bnsaStream.Length) { byte firstByte = (byte)bnsaStream.ReadByte(); bnsaStream.Seek(-1, SeekOrigin.Current); if (firstByte == 0xFF) { break; //End marker. } OAMDataListGroup oamListBlock = new OAMDataListGroup(bnsaStream, oindex); OAMDataListGroups.Add(oamListBlock); //Round up to the next 4 byte boundary (if possible) if (bnsaStream.Position < bnsaStream.Length) { bnsaStream.ReadByte(); //pos++ while (bnsaStream.Position % 4 != 0 && bnsaStream.Position < bnsaStream.Length) { bnsaStream.ReadByte(); //Official game padding. Since we are assuming these are all official, when repacking we should also follow this padding rule. } } else { Console.WriteLine("Not rounding up boundary because at or past end of stream."); } oindex++; } if (bnsaStream.Position != bnsaStream.Length) { Console.WriteLine("...Nothing left to parse but we aren't at the end of the file!"); } else { Console.WriteLine("...Reached end of the file."); } }
/// <summary> /// Converts pointers to references to other parsed BNSA objects. This allows us to export references rather than hard coded values for easy recompilation and editing of the outputted XML. /// </summary> /// <param name="parsedBNSA">BNSA File that has been parsed</param> public void ResolveReferences(BNSAFile parsedBNSA) { //foreach (Palette palette in parsedBNSA.Palettes) //{ // if (palette.Pointer == PalettePointer) // { // ResolvedPalette = palette; // break; // } //} foreach (Tileset tileset in parsedBNSA.Tilesets) { if (tileset.Pointer == TilesetPointer) { ResolvedTileset = tileset; break; } } foreach (OAMDataListGroup oamDataList in parsedBNSA.OAMDataListGroups) { if (oamDataList.Pointer == OAMDataListPointer) { ResolvedOAMDataListGroup = oamDataList; break; } } foreach (MiniAnimGroup minianimgroup in parsedBNSA.MiniAnimGroups) { if (minianimgroup.Pointer == MiniAnimationPointer) { ResolvedMiniAnimGroup = minianimgroup; ResolvedMiniAnimGroup.ResolveReferences(parsedBNSA, this); break; } } //if (ResolvedPalette != null) //{ // Console.WriteLine("----Resolved Palette Reference"); //} //else //{ // Console.WriteLine("----/!\\ Failed to Resolve Palette: 0x"+PalettePointer.ToString("X2")); //} if (ResolvedTileset != null) { Console.WriteLine("----Resolved Tileset Reference"); } else { Console.WriteLine("----/!\\ Failed to Resolve Tileset Pointer 0x" + TilesetPointer.ToString("X2")); throw new Exception("Failed to resolve mini-animation object."); } if (ResolvedMiniAnimGroup != null) { Console.WriteLine("----Resolved MiniAnim Reference"); } else { Console.WriteLine("----/!\\ Failed to Resolve MiniAnim 0x" + MiniAnimationPointer.ToString("X2")); throw new Exception("Failed to resolve mini-animation object."); } if (ResolvedOAMDataListGroup != null) { Console.WriteLine("----Resolved OAM Data List Reference"); } else { Console.WriteLine("----/!\\ Failed to Resolve OAM Data List 0x" + OAMDataListPointer.ToString("X2")); throw new Exception("Failed to resolve mini-animation object."); } //foreach (MiniAnim tileset in parsedBNSA.MiniAnimGroups) //{ // if (minianim.Pointer == TilesetPointer) // { // ResolvedMiniAnim = tileset; // break; // } //} }