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); }
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); }