public ObjectAnimationFrame(ObjectAnimation anim, Data animData) { _animation = anim; _animData = animData; Color[][] palettes = anim.GetPalettes(); try { // Note: this "index" is more like index*2, since it's added directly to the offset // without being multiplied first int oamIndex = (byte)_animData.NextData.GetIntValue(0); // Get entry in oam table for this object Data oamPtr = Project.GetData(_animation.OamTableName, oamIndex); _oamData = Project.GetData(oamPtr.GetValue(0)); // Load sprite images int _numSprites = _oamData.GetIntValue(0); Data data = _oamData.NextData; for (int i = 0; i < _numSprites; i++) { int y = (sbyte)data.GetIntValue(0) - 16; data = data.NextData; int x = (sbyte)data.GetIntValue(0) - 8; data = data.NextData; int tileIndex = data.GetIntValue(0) + _animation.TileIndexBase; data = data.NextData; byte flags = (byte)(data.GetIntValue(0) | _animation.OamFlagsBase); data = data.NextData; ObjectGfxHeaderData gfxHeader = _animation.ObjectGfxHeaderData; int origTileIndex = tileIndex; while (tileIndex >= 0x20) { if (gfxHeader.ShouldHaveNext) { gfxHeader = gfxHeader.NextGfxHeader; tileIndex -= 0x20; } else { throw new InvalidAnimationException("Tileindex out of range (" + tileIndex + ")"); } } int tileOffset = (tileIndex & 0xfe) * 16; Stream gfxStream = gfxHeader.GfxStream; if (gfxStream.Length - tileOffset < 0x20) { throw new InvalidAnimationException("Sprite not defined in gfx data"); } gfxStream.Seek(tileOffset, SeekOrigin.Begin); byte[] gfxData = new byte[0x20]; gfxStream.Read(gfxData, 0, 0x20); Bitmap bitmap = GbGraphics.TileToBitmap(gfxData, palettes[flags & 7], flags); bitmaps.Add(new Tuple <Bitmap, int, int>(bitmap, x, y)); } } catch (InvalidLookupException e) { bitmaps = null; throw new InvalidAnimationException(e); } catch (FormatException e) { bitmaps = null; throw new InvalidAnimationException(e); } }
void ParseLine(string pureLine, List <FileComponent> fileStructure) { string warningString = WarningString; // Helper functions Action <FileComponent> AddComponent = (component) => { fileStructure.Add(component); if (component is Label) { AddLabelToDictionaries(component as Label); } }; Action <FileComponent> AddDataAndPopFileStructure = (data) => { fileStructure.RemoveAt(fileStructure.Count - 1); AddComponent(data); }; Action PopFileStructure = () => { fileStructure.RemoveAt(fileStructure.Count - 1); }; // Sub-function: returns true if a meaning for the token was found. Func <IList <string>, IList <string>, bool> ParseData = (fTokens, fSpacing) => { List <string> standardValues = new List <string>(); // Variables used for some of the goto's int size = -1; for (int j = 1; j < fTokens.Count; j++) { standardValues.Add(fTokens[j]); } switch (fTokens[0].ToLower()) { case ".incbin": { Data d = new Data(Project, fTokens[0], standardValues, -1, this, fSpacing); AddDataAndPopFileStructure(d); break; } case ".dw": if (context == "RAMSECTION" || context == "ENUM") { break; } if (fTokens.Count < 2) { log.Warn(warningString + "Expected .DW to have a value."); break; } size = 2; goto arbitraryLengthData; case ".db": if (context == "RAMSECTION" || context == "ENUM") { break; } if (fTokens.Count < 2) { log.Warn(warningString + "Expected .DB to have a value."); break; } size = 1; goto arbitraryLengthData; case "dwbe": if (fTokens.Count < 2) { log.Warn(warningString + "Expected dwbe to have a value."); break; } size = 2; goto arbitraryLengthData; case "dbrev": if (fTokens.Count < 2) { log.Warn(warningString + "Expected dbrev to have a value."); break; } size = 1; goto arbitraryLengthData; arbitraryLengthData: PopFileStructure(); for (int j = 1; j < fTokens.Count; j++) // Each value is added as individual data { string[] values = { fTokens[j] }; List <string> newfSpacing = new List <string> { fSpacing[0], fSpacing[j], "" }; if (j == fTokens.Count - 1) { newfSpacing[2] = fSpacing[j + 1]; } Data d = new Data(Project, fTokens[0], values, size, this, newfSpacing); if (j != fTokens.Count - 1) { d.EndsLine = false; } if (j != 1) { d.PrintCommand = false; } AddComponent(d); } break; case "db": if (context != "RAMSECTION" && context != "ENUM") { goto default; } address++; break; case "dw": if (context != "RAMSECTION" && context != "ENUM") { goto default; } address += 2; break; case "dsb": if (context != "RAMSECTION" && context != "ENUM") { goto default; } address += Project.EvalToInt(fTokens[1]); break; case "dsw": if (context != "RAMSECTION" && context != "ENUM") { goto default; } address += Project.EvalToInt(fTokens[1]) * 2; break; case "m_animationloop": { Data d = new Data(Project, fTokens[0], standardValues, 2, this, fSpacing); AddDataAndPopFileStructure(d); break; } case "m_rgb16": if (fTokens.Count != 4) { log.Warn(warningString + "Expected " + fTokens[0] + " to take 3 parameters"); break; } { Data d = new RgbData(Project, fTokens[0], standardValues, this, fSpacing); AddDataAndPopFileStructure(d); break; } case "m_gfxheader": case "m_gfxheaderforcemode": if (fTokens.Count < 4 || fTokens.Count > 5) { log.Warn(warningString + "Expected " + fTokens[0] + " to take 3-4 parameters"); break; } { Data d = new GfxHeaderData(Project, fTokens[0], standardValues, this, fSpacing); AddDataAndPopFileStructure(d); break; } case "m_objectgfxheader": { if (!(fTokens.Count >= 3 && fTokens.Count <= 4)) { log.Warn(warningString + "Expected " + fTokens[0] + " to take 2-3 parameters"); break; } Data d = new ObjectGfxHeaderData(Project, fTokens[0], standardValues, this, fSpacing); AddDataAndPopFileStructure(d); break; } case "m_paletteheaderbg": case "m_paletteheaderspr": if (fTokens.Count != 5) { log.Warn(warningString + "Expected " + fTokens[0] + " to take 4 parameters"); break; } { Data d = new PaletteHeaderData(Project, fTokens[0], standardValues, this, fSpacing); AddDataAndPopFileStructure(d); break; } case "m_tilesetheader": if (fTokens.Count != 6) { log.Warn(warningString + "Expected " + fTokens[0] + " to take 5 parameters"); break; } { Data d = new TilesetHeaderData(Project, fTokens[0], standardValues, this, fSpacing); AddDataAndPopFileStructure(d); break; } case "m_tilesetdata": if (fTokens.Count != 2) { log.Warn(warningString + "Expected " + fTokens[0] + " to take 1 parameter"); break; } { Stream file = Project.GetBinaryFile("tilesets/" + Project.GameString + "/" + fTokens[1] + ".bin"); Data d = new Data(Project, fTokens[0], standardValues, (Int32)file.Length, this, fSpacing); AddDataAndPopFileStructure(d); break; } case "m_roomlayoutdata": if (fTokens.Count != 2) { log.Warn(warningString + "Expected " + fTokens[0] + " to take 1 parameter"); break; } { Label l = new Label(this, fTokens[1]); l.Fake = true; AddDataAndPopFileStructure(l); Data d = new Data(Project, fTokens[0], standardValues, -1, this, fSpacing); AddComponent(d); break; } case "m_seasonalarea": { // In season's "areas.s", the m_SeasonalArea macro points to a label which // contains 4 area definitions (one for each season). if (fTokens.Count != 2) { log.Warn(warningString + "Expected " + fTokens[0] + " to take 1 parameter"); break; } // Create a data object considered to have a size of 8 bytes Data d = new Data(Project, fTokens[0], standardValues, 8, this, fSpacing); AddDataAndPopFileStructure(d); break; } case "m_interactiondata": { if (!(fTokens.Count == 2 || fTokens.Count == 4)) { log.Warn(warningString + "Expected " + fTokens[0] + " to take 1 or 3 parameters"); break; } Data d = new Data(Project, fTokens[0], standardValues, 3, this, fSpacing); AddDataAndPopFileStructure(d); break; } case "m_enemydata": { if (!(fTokens.Count == 4 || fTokens.Count == 5)) { log.Warn(warningString + "Expected " + fTokens[0] + " to take 3-4 parameters"); break; } Data d = new Data(Project, fTokens[0], standardValues, 4, this, fSpacing); AddDataAndPopFileStructure(d); break; } default: { Data d = null; // Try object commands for (int j = 0; j < ObjectGroup.ObjectCommands.Length; j++) { string s = ObjectGroup.ObjectCommands[j]; if (s.ToLower() == fTokens[0].ToLower()) { int minParams = ObjectGroup.ObjectCommandMinParams[j]; int maxParams = ObjectGroup.ObjectCommandMaxParams[j]; if (minParams == -1) { minParams = maxParams; } if (maxParams == -1) { maxParams = minParams; } if (fTokens.Count - 1 < minParams || fTokens.Count - 1 > maxParams) { log.Warn(warningString + "Expected " + fTokens[0] + " to take " + minParams + "-" + maxParams + "parameter(s)"); break; } var objectType = (ObjectType)j; d = new ObjectData(Project, fTokens[0], standardValues, this, fSpacing, objectType); break; } } // Try warp sources foreach (string s in WarpSourceData.WarpCommands) { if (s.ToLower() == fTokens[0].ToLower()) { d = new WarpSourceData(Project, fTokens[0], standardValues, this, fSpacing); } } // Try warp dest if (WarpDestData.WarpCommand.ToLower() == fTokens[0].ToLower()) { d = new WarpDestData(Project, fTokens[0], standardValues, this, fSpacing); } if (d != null) { AddDataAndPopFileStructure(d); break; } return(false); } } return(true); }; string pureTrimmedLine = pureLine.Trim(); // If we're in a documentation block, add any comments to the documentation if (context == "DOCUMENTATION") { if (pureTrimmedLine.Length > 0 && pureTrimmedLine[0] == ';') { documentationString += pureLine + '\n'; return; } else { context = ""; AddComponent(new DocumentationFileComponent(this, documentationString)); context = ""; } } // Check if we're starting a documentation block if (pureTrimmedLine.Length >= 2 && pureTrimmedLine.Substring(0, 2) == ";;") // Begin documentation block { if (context == "DOCUMENTATION") { log.Warn(warningString + "Documentation block already open."); } else { context = "DOCUMENTATION"; documentationString = pureLine + '\n'; } return; } string line = pureLine; // Add raw string to file structure, it'll be removed if // a better representation is found fileStructure.Add(new StringFileComponent(this, line, null)); if (line.Trim().Length == 0) { return; } // TODO: split tokens more intelligently, ie: recognize this as one token: $8000 | $03 //string[] tokens = line.Split(new char[] { ' ', '\t'} ); var tup = Tokenize(line); IList <string> tokens = tup.Item1; IList <string> spacing = tup.Item2; if (tokens.Count > 0) { // Check if we're currently skipping over stuff because of .ifdefs if (ifdefCondition == false) { if (tokens[0].ToLower() == ".ifdef") { ifdefDepth++; } else if (tokens[0].ToLower() == ".else" && failedIfdefDepth == ifdefDepth - 1) { ifdefCondition = true; } else if (tokens[0].ToLower() == ".endif") { ifdefDepth--; if (ifdefDepth == failedIfdefDepth) { ifdefCondition = true; } } return; } switch (tokens[0].ToLower()) { // Built-in directives case ".ramsection": { context = "RAMSECTION"; // Find the last token which specifies the name int tokenIndex = 1; while (tokens[tokenIndex][tokens[tokenIndex].Length - 1] != '"') { tokenIndex++; } tokenIndex++; while (tokenIndex < tokens.Count) { if (tokens[tokenIndex] == "BANK") { tokenIndex++; bank = Project.EvalToInt(tokens[tokenIndex++]); } else if (tokens[tokenIndex] == "SLOT") { tokenIndex++; string slotString = tokens[tokenIndex++]; int slot = Project.EvalToInt(slotString); if (slot == 2) { address = 0xc000; } else // Assuming slot >= 3 { address = 0xd000; } } } break; } case ".ends": if (context == "RAMSECTION") { context = ""; } break; case ".enum": context = "ENUM"; address = Project.EvalToInt(tokens[1]); break; // Not supported: "DESC" (descreasing order) case ".ende": if (context == "ENUM") { context = ""; } break; case ".define": { if (tokens.Count < 3) { log.Debug(warningString + "Expected .DEFINE to have a string and a value."); break; } string value = ""; for (int j = 2; j < tokens.Count; j++) { value += tokens[j]; value += " "; } value = value.Trim(); AddDefinition(tokens[1], value); break; } case ".ifdef": if (tokens.Count < 2) { log.Warn(warningString + "Expected .IFDEF to have a value."); break; } ifdefDepth++; if (Project.GetDefinition(tokens[1]) != null) { ifdefCondition = true; } else { ifdefCondition = false; failedIfdefDepth = ifdefDepth - 1; } break; case ".else": if (ifdefDepth == 0) { log.Warn(warningString + "Expected .IFDEF before .ENDIF."); break; } ifdefCondition = false; break; case ".endif": if (ifdefDepth == 0) { log.Warn(warningString + "Expected .IFDEF before .ENDIF."); break; } ifdefDepth--; break; default: { bool isData = ParseData(tokens, spacing); // In ramsections or enums, assume any unidentifiable data is a label. // Technically this should be the case in any context, but it's more // useful for the parser to tell me what it doesn't understand. if (!isData && (tokens[0][tokens[0].Length - 1] == ':' || context == "RAMSECTION" || context == "ENUM")) { // Label string s = tokens[0]; if (tokens[0][tokens[0].Length - 1] == ':') { s = tokens[0].Substring(0, tokens[0].Length - 1); } FileComponent addedComponent; if (context == "RAMSECTION" || context == "ENUM") { AddDefinition(s, address.ToString()); if (context == "RAMSECTION") { AddDefinition(":" + s, bank.ToString()); } PopFileStructure(); StringFileComponent sc = new StringFileComponent(this, tokens[0], spacing); fileStructure.Add(sc); addedComponent = sc; } else { Label label = new Label(this, s, spacing); AddDataAndPopFileStructure(label); addedComponent = label; } if (tokens.Count > 1) // There may be data directly after the label { string[] tokens2 = new string[tokens.Count - 1]; List <string> spacing2 = new List <string>(); addedComponent.EndsLine = false; // Add raw string to file structure, it'll be removed if a better // representation is found fileStructure.Add(new StringFileComponent( this, line.Substring(spacing[0].Length + tokens[0].Length), spacing2)); for (int j = 1; j < tokens.Count; j++) { tokens2[j - 1] = tokens[j]; } for (int j = 1; j < spacing.Count; j++) { spacing2.Add(spacing[j]); } if (!ParseData(tokens2, spacing2)) { log.Debug(warningString + "Error parsing line."); } } } else { // Unknown data log.Debug(warningString + "Did not understand \"" + tokens[0] + "\"."); } } break; } } }