Exemple #1
0
        protected unsafe RomProjectInfo BuildProjectInfo(int[] songIds, string name, string author)
        {
            var projectInfo = new RomProjectInfo();

            projectInfo.maxSong = (byte)(songIds.Length - 1);
            Marshal.Copy(EncodeAndCenterString(name), 0, new IntPtr(projectInfo.name), 28);
            Marshal.Copy(EncodeAndCenterString(author), 0, new IntPtr(projectInfo.author), 28);

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

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

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

                var project = originalProject.DeepClone();
                project.RemoveAllSongsBut(songIds);
                project.SetExpansionAudio(Project.ExpansionNone);

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

                // Load ROM header (16 bytes) + code/tiles (12KB).
                Stream romBinStream = null;

                string romName = "FamiStudio.Rom.rom";
                romName += pal ? "_pal" : "_ntsc";
                if (project.UsesFamiStudioTempo)
                {
                    romName += "_tempo";
                }
                romName += ".nes";

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

                // Build project info + song table of content.
                var projectInfo = new RomProjectInfo();
                projectInfo.maxSong = (byte)(songIds.Length - 1);
                Marshal.Copy(EncodeAndCenterString(name), 0, new IntPtr(projectInfo.name), 28);
                Marshal.Copy(EncodeAndCenterString(author), 0, new IntPtr(projectInfo.author), 28);

                var songTable = new RomSongEntry[MaxSongs];
                for (int i = 0; i < project.Songs.Count; i++)
                {
                    fixed(RomSongEntry *songEntry = &songTable[i])
                    {
                        songEntry->page    = 0;
                        songEntry->address = 0x8000;
                        Marshal.Copy(EncodeAndCenterString(project.Songs[i].Name), 0, new IntPtr(songEntry->name), 28);
                    }
                }

                // Gather DPCM + song data.
                var songDataBytes = new List <byte>();
                var dpcmBaseAddr  = RomDpcmOffset;

                // We will put samples right at the beginning.
                if (project.UsesSamples)
                {
                    // Since we keep the code/engine at f000 all the time, we are limited to 12KB of samples in ROM.
                    var totalSampleSize = project.GetTotalSampleSize();
                    var dpcmPageCount   = Math.Min(MaxDpcmPages, (totalSampleSize + (RomPageSize - 1)) / RomPageSize);

                    // Otherwise we will allocate at least a full page for the samples and use the following mapping:
                    //    0KB -  4KB samples: starts at 0xe000
                    //    4KB -  8KB samples: starts at 0xd000
                    //    8KB - 12KB samples: starts at 0xc000
                    dpcmBaseAddr += (MaxDpcmPages - dpcmPageCount) * RomPageSize;

                    var dpcmBytes = project.GetPackedSampleData();
                    if (dpcmBytes.Length > (MaxDpcmPages * RomPageSize))
                    {
                        Array.Resize(ref dpcmBytes, MaxDpcmPages * RomPageSize);
                    }

                    songDataBytes.AddRange(dpcmBytes);

                    projectInfo.dpcmPageCount = (byte)dpcmPageCount;
                    projectInfo.dpcmPageStart = (byte)0;
                }

                // Export each song individually, build TOC at the same time.
                for (int i = 0; i < project.Songs.Count; i++)
                {
                    var song      = project.Songs[i];
                    int page      = songDataBytes.Count / RomPageSize;
                    int addr      = RomMemoryStart + (songDataBytes.Count & (RomPageSize - 1));
                    var songBytes = new FamitoneMusicFile(FamitoneMusicFile.FamiToneKernel.FamiStudio).GetBytes(project, new int[] { song.Id }, addr, dpcmBaseAddr, MachineType.NTSC);

                    songTable[i].page    = (byte)(page);
                    songTable[i].address = (ushort)(addr);
                    songTable[i].flags   = (byte)(song.UsesDpcm ? 1 : 0);

                    songDataBytes.AddRange(songBytes);
                }

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

                int numPrgBanks = RomMinSize / RomPrgBankSize;

                if (songDataBytes.Count > (RomMinSize - RomCodeSize))
                {
                    numPrgBanks = (songDataBytes.Count + (RomPrgBankSize - 1)) / RomPrgBankSize;
                }

                int padding = (numPrgBanks * RomPrgBankSize) - RomCodeSize - songDataBytes.Count;
                songDataBytes.AddRange(new byte[padding]);

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

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

                // Patch header.
                headerBytes[RomHeaderPrgOffset] = (byte)numPrgBanks;

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

                File.WriteAllBytes(filename, romBytes.ToArray());
            }
            catch
            {
                return(false);
            }

            return(true);
        }