예제 #1
0
        public static bool Merge(HDMATable Table1, HDMATable Table2, out HDMATable Merged)
        {
            Merged = null;
            if (Table1.Count != Table2.Count)
            {
                return(false);
            }

            if (Table1.SequenceEqual(Table2, new HDMATableEntryComparer()))
            {
                Merged = new HDMATable();
                for (int i = 0; i < Table1.Count; i++)
                {
                    List <byte> values = new List <byte>();
                    values.AddRange(Table1[i].Values);
                    values.AddRange(Table2[i].Values);
                    Merged.Add(new HDMATableEntry(TableValueType.db, Table1[i].Scanlines, values.ToArray()));
                }
                if (!Merged.HasEnded())
                {
                    Merged.Add(HDMATableEntry.End);
                }
                return(true);
            }
            return(false);
        }
예제 #2
0
        /// <summary>
        /// Calculates the code for overlapping multiple brightness gradients.
        /// <para>The final code will use the channel of the first BrightnessHDMA object passed.</para>
        /// </summary>
        /// <param name="hdma">An array of BrightnessHDMA object that will be overlapped for the final code.</param>
        /// <returns>The final code.</returns>
        public static string MultiCode(params BrightnessHDMA[] hdma)
        {
            if (hdma == null || hdma.Length == 0)
            {
                return(null);
            }

            int channel = hdma[0].Channel;

            EffectClasses.HDMATable table = new HDMATable(".BrightTable");

            using (Bitmap b = BitmapEffects.OverlapImages(hdma.Select(h => h.EffectImage).ToArray()))
            {
                byte scanlines = 1;
                int  now       = 0;
                int  compare   = 0x0F - (b.GetPixel(0, 0).A / 17);
                for (int y = 1; y < b.Height; y++, scanlines++)
                {
                    now = 0x0F - (b.GetPixel(0, y).A / 17);
                    if (compare != now || scanlines >= 0x80)
                    {
                        table.Add(new HDMATableEntry(TableValueType.db, scanlines, (byte)compare));
                        compare   = now;
                        scanlines = 0;
                    }
                }

                if (table.TotalScanlines != Scanlines)
                {
                    table.Add(new HDMATableEntry(TableValueType.db, scanlines, (byte)now));
                }

                table.Add(HDMATableEntry.End);

                DMAMode        mode       = (table[0].Values.Length == 1 ? DMAMode.P : DMAMode.PP);
                ASMCodeBuilder code       = new ASMCodeBuilder();
                int            channelAdd = 16 * channel;
                table.Name = ".BrightTable";

                code.AppendLabel(INITLabel, "Code to be inserted INIT");
                code.OpenNewBlock();
                code.AppendCode("\tREP #$20", "16 bit mode");
                code.AppendCode("\tLDA #$" + (((Registers.Brightness & 0xFF) << 8) + (int)mode).ToString("X4"));
                code.AppendCode("\tSTA $" + (Registers.DMAMode + channelAdd).ToString("X4"));
                code.AppendCode("\tLDA #" + table.Name, "load high and low byte of table address");
                code.AppendCode("\tSTA $" + (Registers.DMALowByte + channelAdd).ToString("X4"));
                code.AppendCode("\tSEP #$20", "back to 8 bit mode");
                code.AppendCode("\tLDA.b #" + table.Name + ">>16", "load bank byte of table address");
                code.AppendCode("\tSTA $" + (Registers.DMABankByte + channelAdd).ToString("X4"));
                code.AppendCode("\tLDA #$" + (1 << channel).ToString("X2"));
                code.AppendCode("\tTSB $" + RAM.HDMAEnable[RAM.SA1].ToString("X4"), "enable HDMA channel " + channel);
                code.AppendCode("\tRTS");
                code.CloseBlock();
                code.AppendEmptyLine();

                code.AppendTable(table);

                return(code.ToString());
            }
        }
예제 #3
0
        /// <summary>
        /// Draws the image and sets the internal table based on a string array.
        /// Returns every line that couldn't be translated.
        /// </summary>
        /// <param name="Lines">The lines that will be turned into an image</param>
        /// <param name="eightBit">Whether the values should be interpreted as 8 or 5 bit.</param>
        /// <returns>An array of error containing lines.</returns>
        public string[] FromString(string[] Lines, bool eightBit)
        {
            Table = new HDMATable();
            List <string> errorLines = new List <string>();

            foreach (string s in Lines)
            {
                try
                {
                    string[] values = s.Split(", ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    if (values.Length < 2)
                    {
                        throw new ArgumentException("Too few arguments.");
                    }

                    byte[] bytes = new byte[values.Length - 1];
                    for (int i = 1; i < values.Length; i++)
                    {
                        if (!eightBit)
                        {
                            bytes[i - 1] = (byte)((byte)ColorEffect | (0x1F & values[i].ToHexInt()));
                        }
                        else
                        {
                            bytes[i - 1] = (byte)((byte)ColorEffect | values[i].ToHexInt() / 8);
                        }
                    }
                    if (bytes.Any(b => b < (byte)ColorEffect))
                    {
                        throw new Exception("Wrong Convertion");
                    }

                    Table.Add(new HDMATableEntry(TableValueType.db, (byte)values[0].ToHexInt(), bytes));

                    if (Table.TotalScanlines > Scanlines)
                    {
                        break;
                    }
                }
                catch (Exception ex)
                {
#if (DEBUG)
                    Console.WriteLine("ColorHDMA.FromString ex: " + ex.Message);
#endif
                    errorLines.Add(s);
                }
            }

            if (Table.TotalScanlines < Scanlines)
            {
                Table.Add(new HDMATableEntry(TableValueType.db, 1, (byte)ColorEffect));
            }

            Table.Add(HDMATableEntry.End);
            UpdateImage();
            return(errorLines.ToArray());
        }
예제 #4
0
        public string[] FromString(string[] lines)
        {
            Table = new HDMATable();
            List <string> errorLines = new List <string>();

            foreach (string s in lines)
            {
                try
                {
                    string[] values = s.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    if (values.Length < 2)
                    {
                        throw new ArgumentException("Too few arguments.");
                    }
                    byte scanlines = (byte)values[0].ToHexInt();

                    if (scanlines > 0x80 && scanlines - 0x80 != values.Length - 1)
                    {
                        throw new ArgumentException("Invalid use of scanline counter $80+");
                    }

                    byte[] bytes = new byte[values.Length - 1];
                    for (int i = 1; i < values.Length; i++)
                    {
                        bytes[i - 1] = (byte)(values[i].ToHexInt().Range(0, 15));
                    }

                    Table.Add(new HDMATableEntry(TableValueType.db, scanlines, bytes));

                    if (Table.TotalScanlines > Scanlines)
                    {
                        break;
                    }
                }
                catch (Exception ex)
                {
#if (DEBUG)
                    Console.WriteLine("BrightnessHDMA.FromString ex: " + ex.Message);
#endif
                    errorLines.Add(s);
                }
            }

            if (Table.TotalScanlines < Scanlines)
            {
                Table.Add(new HDMATableEntry(TableValueType.db, 1, 0xF));
            }

            Table.Add(HDMATableEntry.End);
            UpdateImage();
            return(errorLines.ToArray());
        }
예제 #5
0
 public void FromTable(HDMATable table)
 {
     if (!table.HasEnded())
     {
         table.Add(HDMATableEntry.End);
     }
     Table = table;
     UpdateImage();
 }
예제 #6
0
        /// <summary>
        ///
        /// </summary>
        internal void UseCollection()
        {
            //http://www.codeproject.com/Articles/20018/Gradients-made-easy
            List <ColorPosition> _list = new List <ColorPosition>(DarknessPositions);

            _list.Sort();

            if (_list[0].Position != 0)
            {
                _list.Insert(0, new ColorPosition(0, 0xF));
            }
            if (_list[_list.Count - 1].Position < Scanlines)
            {
                _list.Add(new ColorPosition(Scanlines, 0xF));
            }

            List <float> positions = new List <float>();
            //positions.Add(0.0f);

            int maxpos = _list.Max(cp => cp.Position);

            foreach (ColorPosition cp in _list)
            {
                positions.Add((float)cp.Position / (float)maxpos);
            }
            //positions.Add(1.0f);

            List <float> factors = new List <float>();

            //factors.Add(0.0f);
            foreach (ColorPosition cp in _list)
            {
                factors.Add((float)cp.Orignal8Bit / 15.0f);
            }
            //factors.Add(1.0f);

            Bitmap bm = new Bitmap(1, maxpos);

            using (Graphics g = Graphics.FromImage(bm))
            {
                Rectangle r = new Rectangle(0, 0, bm.Width, maxpos);
                using (LinearGradientBrush lgb = new LinearGradientBrush(r, Color.FromArgb(_list.Min(cp => cp.Orignal8Bit) * 17, 0, 0),
                                                                         Color.FromArgb(_list.Max(cp => cp.Orignal8Bit) * 17, 0, 0), 90.0f))
                {
                    Blend blend = new Blend();
                    blend.Factors   = factors.ToArray();
                    blend.Positions = positions.ToArray();
                    lgb.Blend       = blend;
                    g.FillRectangle(lgb, r);
                }
            }

            byte Compare = (byte)(bm.GetPixel(0, 0).R / 17);

            Table = new HDMATable();

            for (int y = 0, scan = 1; y < maxpos; y++, scan++)
            {
                byte darkness = (byte)(bm.GetPixel(0, y).R / 17);
                if (Compare != darkness || scan >= 0x80 || y == maxpos - 1)
                {
                    Table.Add(new HDMATableEntry(TableValueType.db, (byte)scan, Compare));
                    Compare = darkness;
                    scan    = 0;
                }

                for (int x = 0; x < bm.Width; x++)
                {
                    bm.SetPixel(x, y, Color.FromArgb(255 - darkness * 17, 0, 0, 0));
                }
            }

            Table.Add(HDMATableEntry.End);

            Bitmap retImg = new Bitmap(256, maxpos);

            using (TextureBrush brush = new TextureBrush(bm, WrapMode.Tile))
                using (Graphics gr = Graphics.FromImage(retImg))
                    gr.FillRectangle(brush, 0, 0, retImg.Width, retImg.Height);

            bm.Dispose();
            EffectImage = retImg;
        }
예제 #7
0
        public bool CheckAndSplitMask(Bitmap mask, ColorMath math)
        {
            //array of with array for each line.
            //each line has entrys with the color and the start position of that color in that line
            ColPos[][] C_Arr = new ColPos[Scanlines][];

            BitmapData data = mask.LockBits(new Rectangle(new Point(0, 0), mask.Size), ImageLockMode.ReadWrite, mask.PixelFormat);

            byte[] bytes = new byte[data.Height * data.Stride];
            System.Runtime.InteropServices.Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);

            for (int y = 0; y < mask.Height; y++)
            {
                Color         ColorToCheck = BitmapEffects.GetPixel(data, bytes, 0, y);         //mask.GetPixel(0, y);
                List <ColPos> CList        = new List <ColPos>();
                CList.Add(new ColPos(ColorToCheck, 0));

                for (int x = 1; x < mask.Width; x++)
                {
                    Color c = BitmapEffects.GetPixel(data, bytes, x, y);
                    if (ColorToCheck == c)                     //mask.GetPixel(x, y))
                    {
                        continue;
                    }

                    ColorToCheck = c;                     //mask.GetPixel(x, y);
                    CList.Add(new ColPos(ColorToCheck, x));
                }
                C_Arr[y] = CList.ToArray();
            }

            mask.UnlockBits(data);

            //if more then two black/white transitions appear in (any) line
            bool black3 = C_Arr.Any((CA => CA.Count(c => c.Color.Name == "ff000000") > 2));
            bool white3 = C_Arr.Any((CA => CA.Count(c => c.Color.Name == "ffffffff") > 2));

            //3 transitions in both won't work eg. black-white-black-white-black-white
            if (black3 && white3)
            {
                return(false);
            }

            int[] BlackLines;
            int[] WhiteLines;

            //get all the lines with 3 black transitions.
            //can't be more then 3 because that would result in more then 3 white trans. in the same line.
            IEnumerable <ColPos[]> Puff = C_Arr.Where((CA => CA.Count(c => c.Color.Name == "ff000000") > 2));

            //an array of indeces to those lines.
            BlackLines = new int[Puff.Count()];
            int i = 0;

            foreach (ColPos[] cp in Puff)
            {
                BlackLines[i] = Array.IndexOf(C_Arr, cp);
                i++;
            }

            //same for white.
            Puff       = C_Arr.Where((CA => CA.Count(c => c.Color.Name == "ffffffff") > 2));
            WhiteLines = new int[Puff.Count()];
            i          = 0;
            foreach (ColPos[] cp in Puff)
            {
                WhiteLines[i] = Array.IndexOf(C_Arr, cp);
                i++;
            }

            Bitmap w1 = BitmapEffects.FromColor(Color.White, 256, 224);
            Bitmap w2 = BitmapEffects.FromColor(Color.White, 256, 224);

            BitmapData w1Data = w1.LockBits(new Rectangle(0, 0, w1.Width, w1.Height), ImageLockMode.ReadWrite, w1.PixelFormat);
            BitmapData w2Data = w2.LockBits(new Rectangle(0, 0, w2.Width, w2.Height), ImageLockMode.ReadWrite, w2.PixelFormat);

            byte[] w1Bytes = new byte[w1Data.Height * w1Data.Stride];
            byte[] w2Bytes = new byte[w2Data.Height * w2Data.Stride];
            System.Runtime.InteropServices.Marshal.Copy(w1Data.Scan0, w1Bytes, 0, w1Bytes.Length);
            System.Runtime.InteropServices.Marshal.Copy(w2Data.Scan0, w2Bytes, 0, w2Bytes.Length);


            Table = new HDMATable(".windowTable");
            HDMATableEntry entry     = null;
            HDMATableEntry lastEntry = null;

            //if any line in this image requires 3 black and 2 white, one window (bm1) has to be inverted.
            bool WindowInverted = C_Arr.Any(cp => (cp.Count(c => c.Color.Name == "ff000000") == 3 &&
                                                   cp.Count(c => c.Color.Name == "ffffffff") == 2));

            for (int y = 0; y < Scanlines; y++)
            {
                entry = new HDMATableEntry(TableValueType.db, (byte)(1 + y - Table.TotalScanlines),
                                           0xFF, 0x00, 0xFF, 0x00);

                int countBlack = C_Arr[y].Count(cp => cp.Color.Name == "ff000000");
                int countWhite = C_Arr[y].Count(cp => cp.Color.Name == "ffffffff");

                Func <int, int, int, bool> BlackFromTill = (window, from, till) =>
                {
                    if (till == 0x100)
                    {
                        till = 0xFF;
                    }

                    if (window == 1)
                    {
                        for (int x = from; x < till; x++)
                        {
                            BitmapEffects.SetPixel(w1Data, ref w1Bytes, x, y, Color.Black);
                        }
                        entry.Values[0] = (byte)from;
                        entry.Values[1] = (byte)till;
                        return(true);
                    }
                    else if (window == 2)
                    {
                        for (int x = from; x < till; x++)
                        {
                            BitmapEffects.SetPixel(w2Data, ref w2Bytes, x, y, Color.Black);
                        }
                        entry.Values[2] = (byte)from;
                        entry.Values[3] = (byte)till;
                        return(true);
                    }
                    return(false);
                };

                if (countBlack == 1 && countWhite == 0)
                {
                    // whole line black.
                    // if window get's inverted, line can be left unchanged.
                    if (!WindowInverted)
                    {
                        BlackFromTill(1, 0, w1.Width);                          // if not, whole line black
                    }
                }
                else if (countBlack == 2 && countWhite == 1)
                {
                    //outer black, inner white (black-white-black)

                    if (WindowInverted)                                               //if window 1 is inverted it can be done using one window.
                    {
                        BlackFromTill(1, C_Arr[y][1].Position, C_Arr[y][2].Position); //The part white in the mask will be black in w1
                    }
                    else                                                              //window 1 not inveted:
                    {
                        BlackFromTill(1, C_Arr[y][0].Position, C_Arr[y][1].Position); // first black part of mask in window 1,
                        BlackFromTill(2, C_Arr[y][2].Position, w2.Width);             // and the second in window 2.
                    }
                }
                else if (countBlack == 3 && countWhite == 2)
                {
                    //black-white-black-white-black
                    //1 window inverted, the other not (not possible otherwise).
                    //meaning window 1 will be inverted.

                    BlackFromTill(1, C_Arr[y][1].Position, C_Arr[y][4].Position);                       // windows 1 will be black from the beginning of the first white
                    BlackFromTill(2, C_Arr[y][2].Position, C_Arr[y][3].Position);                       // till the end of the second. Windows 2 is the middle black part.
                }

                //else if (countBlack == 3 && countWhite == 3)
                //... IMPOSSIBRU <.<

                else if (countBlack == 2 && countWhite == 3)
                {
                    //white-black-white-black-white
                    //both windows have to be uninverted. (or playing with XOR setting)

                    BlackFromTill(1, C_Arr[y][1].Position, C_Arr[y][2].Position);                       // window 1 black for first black part of mask
                    BlackFromTill(2, C_Arr[y][3].Position, C_Arr[y][4].Position);                       // windows 2 black for second part.
                }
                else if (countBlack == 1 && countWhite == 2)
                {
                    //outer white inner black (white-black-white)

                    if (WindowInverted)                                               //if windows 1 is inverted, we have to use window 2 and set window 1 black on the whole line
                    {
                        BlackFromTill(1, 0, w1.Width);                                // window 1 completly black
                        BlackFromTill(2, C_Arr[y][1].Position, C_Arr[y][2].Position); // window 2 black where mask is black.
                    }
                    else
                    {
                        BlackFromTill(1, C_Arr[y][1].Position, C_Arr[y][2].Position);                           // if not inverted, simply use window 1.
                    }
                }
                else if (countBlack == 0 && countWhite == 1)
                {
                    //whole line visible
                    if (WindowInverted)
                    {
                        BlackFromTill(1, 0, w1.Width);                                          // if window 1 inverted, whole line black
                    }
                }
                else if (countBlack == 2 && countWhite == 2)
                {
                    //problem: unknown if black-white-black-white or white-black-white-black
                    #region 2/2
                    if (C_Arr[y][0].Color.Name == "ff000000")                             //if fisrt part black then B-W-B-W
                    {
                        if (WindowInverted)                                               //inverted:
                        {
                            BlackFromTill(1, C_Arr[y][1].Position, w1.Width);             // the -W-B-W area will be black, so inverted makes the first bar black
                            BlackFromTill(2, C_Arr[y][2].Position, C_Arr[y][3].Position); // window 2 simply makes second bar black
                        }
                        else                                                              //not inverted
                        {
                            BlackFromTill(1, C_Arr[y][0].Position, C_Arr[y][1].Position); // first bar in window 1
                            BlackFromTill(2, C_Arr[y][2].Position, C_Arr[y][3].Position); // guess what
                        }
                    }
                    else                                                                  // first bar not black, thus W-B-W-B
                    {
                        if (WindowInverted)                                               //inverted:
                        {
                            BlackFromTill(1, C_Arr[y][0].Position, C_Arr[y][3].Position); // the W-B-W- area will be black, inverting makes the last bar black.
                            BlackFromTill(2, C_Arr[y][1].Position, C_Arr[y][2].Position); // window 2 will be black where the first bar is.
                        }
                        else                                                              //not inverted
                        {
                            BlackFromTill(1, C_Arr[y][1].Position, C_Arr[y][2].Position); // first bar in window 2
                            BlackFromTill(2, C_Arr[y][3].Position, w2.Width);             // ...
                        }
                    }
                    #endregion
                }
                else if (countBlack == 1 && countWhite == 1)
                {
                    //problem: unknown if black-white or white-black
                    if (C_Arr[y][0].Color.Name == "ff000000")                             //first bar is black thus B-W
                    {
                        if (WindowInverted)                                               //inverted:
                        {
                            BlackFromTill(1, C_Arr[y][1].Position, w1.Width);             //set second bar (white) to black due to inverting
                        }
                        else                                                              //not inverted
                        {
                            BlackFromTill(1, C_Arr[y][0].Position, C_Arr[y][1].Position); //first bar (black) in window one to black
                        }
                    }
                    else                                                                  //first bar not black thus W-B
                    {
                        if (WindowInverted)                                               //inverted:
                        {
                            BlackFromTill(1, C_Arr[y][0].Position, C_Arr[y][1].Position); // set white part black;
                        }
                        else                                                              //not inverted
                        {
                            BlackFromTill(1, C_Arr[y][1].Position, w1.Width);             //set second bar black.
                        }
                    }
                }


                if ((lastEntry != null && !entry.Values.SequenceEqual(lastEntry.Values)) || entry.Scanlines == 0x81)
                {
                    Table.Add(lastEntry);
                    entry.Scanlines = 1;
                }
                lastEntry = (HDMATableEntry)entry.Clone();
            }                              //end of for-loop

            Table.Add(lastEntry);          //the last last entry
            Table.Add(HDMATableEntry.End); //the actual end.

            System.Runtime.InteropServices.Marshal.Copy(w1Bytes, 0, w1Data.Scan0, w1Bytes.Length);
            System.Runtime.InteropServices.Marshal.Copy(w2Bytes, 0, w2Data.Scan0, w2Bytes.Length);
            w1.UnlockBits(w1Data);
            w2.UnlockBits(w2Data);

            math.WindowingMask1 = w1;
            math.WindowingMask2 = w2;

            math.Window1Inverted = WindowInverted ? (WindowingLayers)0x3F : 0;

            return(true);
        }
예제 #8
0
        public override string Code(int channel, HDMATable table, bool sa1)
        {
            int Base        = 0x4300 + (channel * 0x10);
            int Register    = (int)Layers;
            int BaseAddress = RAM.Layer1X[sa1] + ((int)Layers - (int)LayerRegister.Layer1_X) * 2;
            int RegMode     = ((Register & 0xFF) << 8) + GetMode(true, DMAMode.PP);

            var       grouped    = Bars.GroupBy(b => b.Multiplier);
            HDMATable _table     = new HDMATable("ParallaxTable_" + DateTime.Now.ToString("HHmmssfff"));
            int       tableInRAM = grouped.Count() * 2;

            Dictionary <int, int> multiplierAddressMapping = new Dictionary <int, int>();
            int addresseForOne = 0;             //address that has the scrollrate of 1 to finish the table.


            string tableString = "The Table takes up " + tableInRAM + " bytes of the free RAM\n" +
                                 "It ranges from $" + FreeRAM.ToString("X6") + " - $" + (FreeRAM + tableInRAM - 1).ToString("X6") + " (both addresses included)";

            ASMCodeBuilder CodeBuilder = new ASMCodeBuilder();

            if (Original.Height > Scanlines)
            {
                CodeBuilder.AppendCommentLine("IMPORTANT! Please edit the JMP command below for the level you use this on");
            }
            CodeBuilder.AppendEmptyLine();

            CodeBuilder.OpenNewBlock();
            CodeBuilder.AppendLabel(INITLabel, "This section is to be used in the INIT code of levelASM");
            CodeBuilder.AppendCode("REP #$20");
            CodeBuilder.AppendCode("LDA #$" + RegMode.ToASMString(), "Use indeirect and mode " + (int)DMAMode.PP + " on register " + Register.ToASMString());
            CodeBuilder.AppendCode("STA $" + Base.ToASMString(), "43" + Channel + "0 = Mode, 43" + Channel + "1 = Register");

            if (Original.Height <= Scanlines)
            {
                CodeBuilder.AppendCode("LDA #" + _table.Name, "Address of HDMA table, get high and low byte");
                CodeBuilder.AppendCode("STA $" + (Base + 2).ToASMString(), "43" + Channel + "2 = Low-Byte of table, 43" + Channel + "3 = High-Byte of table");
            }

            CodeBuilder.AppendCode("SEP #$20");
            CodeBuilder.AppendCode("LDA.b #" + _table.Name + ">>16", "Address of HDMA table, get bank byte");
            CodeBuilder.AppendCode("STA $" + (Base + 4).ToASMString(), "43" + Channel + "4 = Bank-Byte of table");

            CodeBuilder.AppendCode("LDA #$" + (FreeRAM >> 16).ToASMString(), "Address of indirect table in RAM bank byte");
            CodeBuilder.AppendCode("STA $" + (Base + 7).ToASMString(), "43" + Channel + "4 = Bank-Byte of indirect table");

            CodeBuilder.AppendCode("LDA #$" + (0x01 << Channel).ToASMString());
            CodeBuilder.AppendCode("TSB $" + RAM.HDMAEnable[sa1].ToASMString() + "|!addr", "Enable HDMA channel " + Channel);

            if (Original.Height > Scanlines)
            {
                CodeBuilder.AppendCode("JMP level105_" + MAINLabel.TrimStart('.'), "Jump to main code" + MAINSeperator);
            }
            else
            {
                CodeBuilder.AppendCode("RTL", "Return" + MAINSeperator);
            }
            CodeBuilder.CloseBlock();

            CodeBuilder.AppendCommentLine(tableString);
            CodeBuilder.AppendEmptyLine();

            CodeBuilder.OpenNewBlock();
            CodeBuilder.AppendLabel(MAINLabel, "This section is to be used in the MAIN code of levelASM");
            CodeBuilder.AppendCode("REP #$20", "16 bit action starts here. (To load the x position of the BG)");
            CodeBuilder.CloseBlock();

            //index for HDMA table entry calculation
            if (Original.Height > Scanlines)
            {
                CodeBuilder.OpenNewBlock();
                CodeBuilder.AppendCode("LDA $" + (BaseAddress + 2).ToASMString(), "Get Y position of BG");
                CodeBuilder.AppendCode("ASL", "times 2");
                CodeBuilder.AppendCode("CLC : ADC $" + (BaseAddress + 2).ToASMString(), "+1 = times 3");
                CodeBuilder.AppendCode("CLC : ADC #" + _table.Name + "+3", "plus Address of HDMA table +3");
                //CodeBuilder.AppendCode("INC : INC : INC", "I have no idea why, but the result is off by 1 entry (3 bytes) so this is to fix it.");
                CodeBuilder.AppendCode("STA $" + (Base + 2).ToASMString(), "43" + Channel + "2 = Low-Byte of table, 43" + Channel + "3 = High-Byte of table");
                CodeBuilder.CloseBlock();
            }

            CodeBuilder.AppendEmptyLine();
            int i = 0;

            //grouped by multiplier
            foreach (var bar in grouped)
            {
                CodeBuilder.OpenNewBlock();
                if (bar.Key != 0.0)
                {
                    CodeBuilder.AppendCode("LDA $" + BaseAddress.ToASMString(), "Load BG x Position");
                }
                CodeBuilder.AppendCode(ParallaxHDMAEntry.LsrAsl(bar.Key), "Multiplied by " + bar.Key);
                var windGroup = bar.GroupBy(b => b.Autospeed);

                foreach (var singleBar in windGroup)
                {
                    //if there is no wind, don't add or preserve A
                    if (singleBar.ElementAt(0).Autoscroll&& singleBar.Key != 0)
                    {
                        CodeBuilder.AppendCode("PHA", "Preserve A (current multiplication result)");
                        CodeBuilder.AppendCode("CLC : ADC #$" + singleBar.Key.ToString("X4"), "Add rate.");
                    }
                    CodeBuilder.AppendCode("STA $" + (FreeRAM + i).ToASMString(), "Store to FreeRAM for indirect HDMA");
                    //also don't restore A if there is no wind.
                    if (singleBar.ElementAt(0).Autoscroll&& singleBar.Key != 0)
                    {
                        CodeBuilder.AppendCode("PLA", "Restore A (current multiplication result)");
                    }

                    //keep track of the address for normal scrolling behaviour (multiplier = 1, no auto scroll)
                    if (bar.Key == 1.0 && singleBar.ElementAt(0).Autoscroll&& singleBar.Key != 0)
                    {
                        addresseForOne = FreeRAM + i;
                    }

                    //remember which address has this specs in dictionary with hashcode
                    multiplierAddressMapping.Add(singleBar.ElementAt(0).GetHashCode(), FreeRAM + i);
                    i += 2;
                }
                CodeBuilder.CloseBlock();
            }

            //if the normal scrolling hasn't been set yet, make one.
            if (addresseForOne == 0)
            {
                CodeBuilder.OpenNewBlock();
                CodeBuilder.AppendCode("LDA $" + BaseAddress.ToASMString(), "Load BG x Position");
                CodeBuilder.AppendCode("STA $" + (FreeRAM + i).ToASMString(), "Store to FreeRAM for indirect HDMA");
                CodeBuilder.CloseBlock();
                addresseForOne = FreeRAM + i;
                i += 2;
            }

            //build the table
            foreach (var bar in Bars)
            {
                //fetch indirect HDMA address
                int adr = multiplierAddressMapping[bar.GetHashCode()] & 0xFFFF;
                //split it
                byte low  = (byte)(adr & 0xFF);
                byte high = (byte)((adr >> 8) & 0xFF);

                //make as many 1 scanline high entries as needed.
                var entry = new HDMATableEntry(TableValueType.dw, 1, low, high);
                _table.AddRange(Enumerable.Repeat <HDMATableEntry>(entry, bar.Scanline));
            }

            //if there aren't enough entries for the whole screen yet, fill it up with scrollrate 1.
            if (_table.Count < Original.Height)
            {
                //fetch indirect HDMA address for normal scrolling
                int adr = addresseForOne & 0xFFFF;
                //split it
                byte low  = (byte)(adr & 0xFF);
                byte high = (byte)((adr >> 8) & 0xFF);

                var entry = new HDMATableEntry(TableValueType.dw, 1, low, high);
                _table.AddRange(Enumerable.Repeat <HDMATableEntry>(entry, Original.Height - _table.Count));
            }

            //cut down table
            for (int l = _table.Count - 1; l >= Original.Height; l--)
            {
                _table.RemoveAt(l);
            }

            //add end.
            _table.Add(HDMATableEntry.End);

            CodeBuilder.AppendCode("SEP #$20", "Back to 8bit");
            CodeBuilder.AppendCode("RTL", "Return");
            CodeBuilder.CloseBlock();

            CodeBuilder.AppendEmptyLine();
            CodeBuilder.OpenNewBlock();
            CodeBuilder.AppendTable(_table);
            CodeBuilder.CloseBlock();

            return(CodeBuilder.ToString());           // +"\n\n" + tableString;
        }