Exemple #1
0
        public GLBitmapAtlas CreateBitmapAtlasFromResources(string[] names)
        {
            var bitmaps      = new Bitmap[names.Length];
            var elementSizeX = 0;
            var elementSizeY = 0;

            for (int i = 0; i < names.Length; i++)
            {
                var bmp = LoadBitmapFromResourceWithScaling(names[i]);

                elementSizeX = Math.Max(elementSizeX, bmp.Width);
                elementSizeY = Math.Max(elementSizeY, bmp.Height);

                bitmaps[i] = bmp;
            }

            var elementsPerRow = MaxAtlasResolution / elementSizeX;
            var numRows        = Utils.DivideAndRoundUp(names.Length, elementsPerRow);
            var atlasSizeX     = elementsPerRow * elementSizeX;
            var atlasSizeY     = numRows * elementSizeY;
            var textureId      = CreateEmptyTexture(atlasSizeX, atlasSizeY, true);
            var elementRects   = new Rectangle[names.Length];

            GLES11.GlBindTexture(GLES11.GlTexture2d, textureId);

            for (int i = 0; i < names.Length; i++)
            {
                var bmp = bitmaps[i];

                var row = i / elementsPerRow;
                var col = i % elementsPerRow;

                elementRects[i] = new Rectangle(
                    col * elementSizeX,
                    row * elementSizeY,
                    bmp.Width,
                    bmp.Height);

                GLUtils.TexSubImage2D(GLES11.GlTexture2d, 0, elementRects[i].X, elementRects[i].Y, bmp);
                bmp.Recycle();
            }

            return(new GLBitmapAtlas(textureId, atlasSizeX, atlasSizeY, elementRects, true, true));
        }
Exemple #2
0
        const int MaxDpcmSize         = 0x2c00; // 11KB

        public unsafe bool Save(Project originalProject, string filename, int[] songIds, string name, string author, bool pal)
        {
#if !DEBUG
            try
#endif
            {
                if (songIds.Length == 0)
                {
                    return(false);
                }

                Debug.Assert(!originalProject.UsesAnyExpansionAudio || !pal);

                if (songIds.Length > MaxSongs)
                {
                    Array.Resize(ref songIds, MaxSongs);
                }

                var project = originalProject.DeepClone();
                project.DeleteAllSongsBut(songIds);
                project.SetExpansionAudioMask(ExpansionType.NoneMask);

                var headerBytes = new byte[RomHeaderLength];
                var codeBytes   = new byte[RomCodeAndTocSize + RomTileSize];

                // Load ROM header (16 bytes) + code/tiles (12KB).
                string romName = "FamiStudio.Rom.rom";
                if (project.UsesFamiTrackerTempo)
                {
                    romName += "_famitracker";
                }
                romName += pal ? "_pal" : "_ntsc";
                romName += ".nes";

                var romBinStream = typeof(RomFile).Assembly.GetManifestResourceStream(romName);
                romBinStream.Read(headerBytes, 0, RomHeaderLength);
                romBinStream.Seek(-RomCodeAndTocSize - RomTileSize, SeekOrigin.End);
                romBinStream.Read(codeBytes, 0, RomCodeAndTocSize + RomTileSize);

                Log.LogMessage(LogSeverity.Info, $"ROM code and graphics size: {codeBytes.Length} bytes.");

                // Build project info + song table of content.
                var projectInfo = BuildProjectInfo(songIds, name, author);
                var songTable   = BuildSongTableOfContent(project);

                // Gathersong data.
                var songBanks = new List <List <byte> >();

                // Export each song individually, build TOC at the same time.
                for (int i = 0; i < project.Songs.Count; i++)
                {
                    if (i == MaxSongs)
                    {
                        Log.LogMessage(LogSeverity.Warning, $"Too many songs. There is a hard limit of {MaxSongs} at the moment. Ignoring any extra songs.");
                        break;
                    }

                    var song      = project.Songs[i];
                    var songBytes = new FamitoneMusicFile(FamiToneKernel.FamiStudio, false).GetBytes(project, new int[] { song.Id }, RomSongDataStart, RomDpcmStart, pal ? MachineType.PAL : MachineType.NTSC);

                    if (songBytes.Length > MaxSongSize)
                    {
                        Log.LogMessage(LogSeverity.Warning, $"Song {song.Name} has a size of {songBytes.Length}, which is larger than the maximum allowed for ROM export ({MaxSongSize}). Truncating.");
                        Array.Resize(ref songBytes, MaxSongSize);
                    }

                    var numBanks = Utils.DivideAndRoundUp(songBytes.Length, RomBankSize);
                    Debug.Assert(numBanks <= 2);

                    var songBank = songBanks.Count;
                    var songAddr = RomSongDataStart;

                    // If single bank, look for an existing bank with some free space at the end.
                    if (numBanks == 1)
                    {
                        var foundExistingBank = false;

                        for (int j = 0; j < songBanks.Count; j++)
                        {
                            var freeSpace = RomBankSize - songBanks[j].Count;
                            if (songBytes.Length <= freeSpace)
                            {
                                songBank  = j;
                                songAddr  = RomSongDataStart + songBanks[j].Count;
                                songBytes = new FamitoneMusicFile(FamiToneKernel.FamiStudio, false).GetBytes(project, new int[] { song.Id }, songAddr, RomDpcmStart, pal ? MachineType.PAL : MachineType.NTSC);
                                Debug.Assert(songBytes.Length <= freeSpace);
                                foundExistingBank = true;
                                break;
                            }
                        }

                        // No free space found, allocation a new partial bank.
                        if (!foundExistingBank)
                        {
                            songBanks.Add(new List <byte>());
                        }

                        songBanks[songBank].AddRange(songBytes);
                    }
                    else
                    {
                        // When a song uses 2 banks, allocate a new full one and a partial one.
                        var bank0 = new List <byte>();
                        var bank1 = new List <byte>();

                        for (int j = 0; j < RomBankSize; j++)
                        {
                            bank0.Add(songBytes[j]);
                        }
                        for (int j = RomBankSize; j < songBytes.Length; j++)
                        {
                            bank1.Add(songBytes[j]);
                        }

                        songBanks.Add(bank0);
                        songBanks.Add(bank1);
                    }

                    songTable[i].bank    = (byte)songBank;
                    songTable[i].address = (ushort)songAddr;
                    songTable[i].flags   = (byte)(song.UsesDpcm ? 1 : 0);

                    Log.LogMessage(LogSeverity.Info, $"Song '{song.Name}' size: {songBytes.Length} bytes.");
                }

                //File.WriteAllBytes("D:\\debug.bin", songDataBytes.ToArray());

                // Add extra empty banks if we haven't reached the minimum.
                if (songBanks.Count < RomMinNumberBanks)
                {
                    for (int i = songBanks.Count; i < RomMinNumberBanks; i++)
                    {
                        songBanks.Add(new List <byte>());
                    }
                }
                else if ((songBanks.Count & 1) != 0)
                {
                    songBanks.Add(new List <byte>());
                }

                // Build final song bank data.
                var songBanksBytes = new byte[songBanks.Count * RomBankSize];
                for (int i = 0; i < songBanks.Count; i++)
                {
                    Array.Copy(songBanks[i].ToArray(), 0, songBanksBytes, i * RomBankSize, songBanks[i].Count);
                }

                // Patch in code (project info and song table are after the code, 0xf000).
                Marshal.Copy(new IntPtr(&projectInfo), codeBytes, RomTocOffset, sizeof(RomProjectInfo));

                for (int i = 0; i < MaxSongs; i++)
                {
                    fixed(RomSongEntry *songEntry = &songTable[i])
                    Marshal.Copy(new IntPtr(songEntry), codeBytes, RomTocOffset + sizeof(RomProjectInfo) + i * sizeof(RomSongEntry), sizeof(RomSongEntry));
                }

                // Patch header (iNES header always counts in 16KB banks, MMC3 counts in 8KB banks)
                headerBytes[RomHeaderPrgOffset] = (byte)((songBanks.Count + RomCodeDpcmNumBanks) * RomBankSize / 0x4000);

                // Build final ROM and save.
                var romBytes = new List <byte>();
                romBytes.AddRange(headerBytes);
                romBytes.AddRange(songBanksBytes);

                // Samples are at the end, right before the source engine code. MMC3 second to last and last banks respectively.
                if (project.UsesSamples)
                {
                    // Since we keep the code/engine at f000 all the time, we are limited to 12KB of samples in ROM.
                    var dpcmBytes = project.GetPackedSampleData();

                    Log.LogMessage(LogSeverity.Info, $"DPCM size: {dpcmBytes.Length} bytes.");

                    if (dpcmBytes.Length > MaxDpcmSize)
                    {
                        Log.LogMessage(LogSeverity.Warning, $"DPCM samples size ({dpcmBytes.Length}) is larger than the maximum allowed for ROM export ({MaxDpcmSize}). Truncating.");
                    }

                    // Always allocate the full 11KB of samples.
                    Array.Resize(ref dpcmBytes, MaxDpcmSize);

                    romBytes.AddRange(dpcmBytes);
                }
                else
                {
                    romBytes.AddRange(new byte[MaxDpcmSize]);
                }

                romBytes.AddRange(codeBytes);

                File.WriteAllBytes(filename, romBytes.ToArray());

                Log.LogMessage(LogSeverity.Info, $"ROM export successful, final file size {romBytes.Count} bytes.");
            }
#if !DEBUG
            catch (Exception e)
            {
                Log.LogMessage(LogSeverity.Error, "Please contact the developer on GitHub!");
                Log.LogMessage(LogSeverity.Error, e.Message);
                Log.LogMessage(LogSeverity.Error, e.StackTrace);
                return(false);
            }
#endif

            return(true);
        }
Exemple #3
0
        public GLBitmapAtlas CreateBitmapAtlasFromResources(string[] names)
        {
            var bitmaps      = new Bitmap[names.Length];
            var elementSizeX = 0;
            var elementSizeY = 0;

            for (int i = 0; i < names.Length; i++)
            {
                var bmp = LoadBitmapFromResourceWithScaling(names[i]);

                elementSizeX = Math.Max(elementSizeX, bmp.Width);
                elementSizeY = Math.Max(elementSizeY, bmp.Height);

                bitmaps[i] = bmp;
            }

            var elementsPerRow = MaxAtlasResolution / elementSizeX;
            var numRows        = Utils.DivideAndRoundUp(names.Length, elementsPerRow);
            var atlasSizeX     = elementsPerRow * elementSizeX;
            var atlasSizeY     = numRows * elementSizeY;
            var textureId      = CreateEmptyTexture(atlasSizeX, atlasSizeY);
            var elementRects   = new Rectangle[names.Length];

            GL.BindTexture(TextureTarget.Texture2D, textureId);

            for (int i = 0; i < names.Length; i++)
            {
                var bmp = bitmaps[i];

                var row = i / elementsPerRow;
                var col = i % elementsPerRow;

                elementRects[i] = new Rectangle(
                    col * elementSizeX,
                    row * elementSizeY,
                    bmp.Width,
                    bmp.Height);

#if FAMISTUDIO_WINDOWS
                var bmpData =
                    bmp.LockBits(
                        new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
                        System.Drawing.Imaging.ImageLockMode.ReadOnly,
                        System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                var ptr    = bmpData.Scan0;
                var stride = bmpData.Stride;
                var format = PixelFormat.Bgra;
#else
                var ptr    = bmp.Pixels;
                var stride = bmp.Rowstride;
                var format = PixelFormat.Rgba;
#endif

                Debug.Assert(stride == bmp.Width * 4);

                GL.TexSubImage2D(TextureTarget.Texture2D, 0, elementRects[i].X, elementRects[i].Y, bmp.Width, bmp.Height, format, PixelType.UnsignedByte, ptr);

#if FAMISTUDIO_WINDOWS
                bmp.UnlockBits(bmpData);
#endif
                bmp.Dispose();
            }

            return(new GLBitmapAtlas(textureId, atlasSizeX, atlasSizeY, elementRects));
        }