/// <summary> /// Gets the byte representation of the command /// </summary> /// <param name="settings">The game settings</param> /// <returns>The command bytes</returns> public byte[] ToBytes(GameSettings settings) { using (var context = new Context(settings)) { // Create a memory stream using (var memStream = new MemoryStream()) { // Stream key const string key = "PC_EventCommand"; // Add the stream context.AddStreamFile(key, memStream); // TODO: Pass in this instance // Serialize the command FileFactory.Write <R1_EventCommandCollection>(key, this, context); // Return the bytes return(memStream.ToArray()); } } }
protected void CreateISO(Context context) { // Get the xml file const string xmlFilePath = "disc.xml"; // Close the context so the files can be accessed by other processes context.Close(); // Create a temporary file for the LBA log using (var lbaLogFile = new TempFile()) { // Recalculate the LBA for the files on the disc ProcessHelpers.RunProcess(Settings.Tool_mkpsxiso_filePath, new string[] { "-lba", ProcessHelpers.GetStringAsPathArg(lbaLogFile.TempPath), // Specify LBA log path "-noisogen", // Don't generate an ISO now xmlFilePath // The xml path }, workingDir: context.BasePath); // Read the LBA log using (var lbaLogStream = lbaLogFile.OpenRead()) { using (var reader = new StreamReader(lbaLogStream)) { // Skip initial lines for (int i = 0; i < 8; i++) { reader.ReadLine(); } var logEntries = new List <LBALogEntry>(); var currentDirs = new List <string>(); // Read all log entries while (!reader.EndOfStream) { var line = reader.ReadLine(); if (line == null) { break; } var words = line.Split(' ').Where(x => !String.IsNullOrWhiteSpace(x)).ToArray(); if (!words.Any()) { continue; } var entry = new LBALogEntry(words, currentDirs); logEntries.Add(entry); if (entry.EntryType == LBALogEntry.Type.Dir) { currentDirs.Add(entry.Name); } if (entry.EntryType == LBALogEntry.Type.End) { currentDirs.RemoveAt(currentDirs.Count - 1); } } // Read the game exe var exe = FileFactory.Read <R1_PS1_Executable>(ExeFilePath, context); // Update every file path in the file table foreach (var fileEntry in exe.FileTable) { // Get the matching entry var entry = logEntries.FirstOrDefault(x => x.FullPath == fileEntry.FilePath); if (entry == null) { if (!String.IsNullOrWhiteSpace(fileEntry.FilePath)) { Debug.Log($"LBA not updated for {fileEntry.FilePath}"); } continue; } // Update the LBA and size fileEntry.LBA = entry.LBA; fileEntry.FileSize = (uint)entry.Bytes; } // Write the game exe FileFactory.Write <R1_PS1_Executable>(ExeFilePath, context); } } } // Close context again so the exe can be accessed context.Close(); // Create a new ISO ProcessHelpers.RunProcess(Settings.Tool_mkpsxiso_filePath, new string[] { "-y", // Set to always overwrite xmlFilePath // The xml path }, workingDir: context.BasePath, logInfo: false); }
/// <summary> /// Saves the specified level /// </summary> /// <param name="context">The serialization context</param> /// <param name="lvl">The level</param> public override UniTask SaveLevelAsync(Context context, Unity_Level lvl) { // Menu levels can't be saved if (context.Settings.R1_World == R1_World.Menu) { return(UniTask.CompletedTask); } // Get the level file path var lvlPath = GetLevelFilePath(context.Settings); // Get the level data var lvlData = context.GetMainFileObject <R1_PS1_LevFile>(lvlPath); // Get the object manager var objManager = (Unity_ObjectManager_R1)lvl.ObjManager; // Update the tiles for (int y = 0; y < lvlData.MapData.Height; y++) { for (int x = 0; x < lvlData.MapData.Width; x++) { // Set the tile lvlData.MapData.Tiles[y * lvlData.MapData.Width + x] = lvl.Maps[0].MapTiles[y * lvlData.MapData.Width + x].Data; } } var newEvents = lvl.EventData.Cast <Unity_Object_R1>().Select(e => { var ed = e.EventData; if (ed.PS1Demo_Unk1 == null) { ed.PS1Demo_Unk1 = new byte[40]; } if (ed.Unk_98 == null) { ed.Unk_98 = new byte[5]; } // TODO: Do this in the Unity_Object instead ed.ImageDescriptorCount = (ushort)objManager.DES[e.DESIndex].Data.ImageDescriptors.Length; ed.AnimDescriptorCount = (byte)objManager.DES[e.DESIndex].Data.Graphics.Animations.Count; // TODO: Get from DESData in obj manager instead? ed.ImageDescriptors = FileFactory.Read <ObjectArray <R1_ImageDescriptor> >(ed.ImageDescriptorsPointer, context, (s, o) => o.Length = ed.ImageDescriptorCount).Value; ed.AnimDescriptors = FileFactory.Read <ObjectArray <R1_PS1_AnimationDescriptor> >(ed.AnimDescriptorsPointer, context, (s, o) => o.Length = ed.AnimDescriptorCount).Value; ed.ETA = context.Cache.FromOffset <R1_PS1_ETA>(ed.ETAPointer); // TODO: Update this //ed.ImageBuffer = des.ImageBuffer; return(ed); }).ToArray(); var newEventLinkTable = objManager.LinkTable.Select(x => (byte)x).ToArray(); // Relocate pointers to a new block of data we append to the level file UpdateAndFillDataBlock(lvlData.Offset + lvlData.FileSize, lvlData.EventData, newEvents, newEventLinkTable, context.Settings); // TODO: When writing make sure that ONLY the level file gets recreated - do not touch the other files (ignore DoAt if the file needs to be switched based on some setting?) // Save the file FileFactory.Write <R1_PS1_LevFile>(lvlPath, context); // Create ISO for the modified data CreateISO(context); return(UniTask.CompletedTask); }
public async UniTask ImportDESETA(GameSettings settings, bool edu) { // TODO: Not hard-code these? const int desAllfixCount = 9; const int etaAllfixCount = 5; int otherDesAllfixCount = edu ? 8 : 7; int otherEtaAllfixCount = edu ? 5 : 4; // Load in the JSON files to get our mappings await LevelEditorData.InitAsync(settings, true); using (var context = new Context(settings)) { GameSettings otherSettings; if (!edu) { // Find the first Rayman 1 version that the user has actually set up. var mode = EnumHelpers.GetValues <GameModeSelection>().Where(x => x.GetAttribute <GameModeAttribute>().EngineVersion == EngineVersion.R1_PC).First(x => Settings.GameDirectories.ContainsKey(x) && Directory.Exists(Settings.GameDirectories[x])); otherSettings = new GameSettings(mode, Settings.GameDirectories[mode], settings.World, settings.Level); } else { otherSettings = new GameSettings(GameModeSelection.RaymanEducationalPC, Settings.GameDirectories[GameModeSelection.RaymanEducationalPC], settings.World, settings.Level); } using (var otherContext = new Context(otherSettings)) { // Create manager for the other game, and load its files. R1_PCBaseManager otherGame = (R1_PCBaseManager)otherSettings.GetGameManager; if (edu) { otherContext.Settings.EduVolume = otherGame.GetLevels(otherContext.Settings).First().Name; } // Loop through the worlds. for (int w = 1; w < 7; w++) { context.Settings.World = otherContext.Settings.World = w; var wldPath = GetWorldFilePath(context.Settings); await LoadFilesAsync(context); await otherGame.LoadFilesAsync(otherContext); // Load our WLD file and the other game's. var wld = FileFactory.Read <R1_PC_WorldFile>(wldPath, context); var otherWld = FileFactory.Read <R1_PC_WorldFile>(otherGame.GetWorldFilePath(otherContext.Settings), otherContext); // Get the list of existing ETA and DES files so we know what's missing. var desNames = wld.DESFileNames.ToArray(); var etaNames = wld.ETAFileNames.ToArray(); // Use the tables to get mappings from the other game onto this one. var desMappings = new Dictionary <int, string>(); var etaMappings = new Dictionary <int, string>(); var r1wld = otherContext.Settings.R1_World; var desNameTable = otherGame.GetDESNameTable(otherContext); var etaNameTable = otherGame.GetETANameTable(otherContext); // Go through the other game's DES and ETA name tables, and see if any one // is missing from Designer. for (int iDes = 0; iDes < desNameTable.Length; iDes++) { var desName = desNameTable[iDes]; if ((desName == null) || (desName == "N/A")) { continue; } if (!desNames.Contains($"{desName}.DES")) { // The DES is specified in the JSON file, but doesn't exist in the WLD file. // Add it to the copy list. desMappings[iDes] = $"{desName}.DES"; Debug.Log($"Mapping DES {iDes} to {desName}"); } } for (int iEta = 0; iEta < etaNameTable.Length; iEta++) { var etaName = etaNameTable[iEta]; if ((etaName == null) || (etaName == "N/A")) { continue; } if (!etaNames.Contains($"{etaName}.ETA")) { // The ETA is specified in the JSON file, but doesn't exist in the WLD file. // Add it to the copy list. etaMappings[iEta] = $"{etaName}.ETA"; Debug.Log($"Mapping ETA {iEta} to {etaName}"); } } // Now that we've set up the mappings, carry out the copies! var newDesItems = wld.DesItems.ToList(); foreach (var mapping in desMappings) { Debug.Log($"Attempting to port DES {mapping.Key} -> {mapping.Value}"); newDesItems.Add(otherWld.DesItems[mapping.Key - otherDesAllfixCount - 1]); wld.DesItemCount = (ushort)newDesItems.Count; wld.DESFileNames[wld.DesItemCount + desAllfixCount - 1] = mapping.Value; } wld.DesItems = newDesItems.ToArray(); var newEtaItems = wld.Eta.ToList(); foreach (var mapping in etaMappings) { Debug.Log($"Attempting to port ETA {mapping.Key} -> {mapping.Value}"); newEtaItems.Add(otherWld.Eta[mapping.Key - otherEtaAllfixCount]); wld.ETAFileNames[newEtaItems.Count + etaAllfixCount - 1] = mapping.Value; } wld.Eta = newEtaItems.ToArray(); // Save the WLD FileFactory.Write <R1_PC_WorldFile>(wldPath, context); } } } // Beef up the memory allocation if necessary. await IncreaseMemAlloc(settings); }
public static async UniTask MapperToRDAsync(GameSettings inputSettings, GameSettings outputSettings) { using (var inputContext = new Context(inputSettings)) { using (var outputContext = new Context(outputSettings)) { // Create managers var mapperManager = new R1_Mapper_Manager(); var rdManager = new R1_Kit_Manager(); // Load files to context await mapperManager.LoadFilesAsync(inputContext); await rdManager.LoadFilesAsync(outputContext); // Load the mapper level var inputLev = (await mapperManager.LoadAsync(inputContext, false)); var inputMap = inputLev.Maps[0]; var inputObjManager = (Unity_ObjectManager_R1)inputLev.ObjManager; // Load the editor manager data for the output level var outData = await rdManager.LoadAsync(outputContext, true); var outputObjManager = (Unity_ObjectManager_R1)outData.ObjManager; // Load a dummy PC level as a base var outLev = FileFactory.Read <R1_PC_LevFile>(rdManager.GetLevelFilePath(outputSettings), outputContext); // TODO: Set background data // Set map data outLev.MapData.Width = inputMap.Width; outLev.MapData.Height = inputMap.Height; //outLev.MapData.ColorPalettes = new RGB666Color[][] //{ // FileFactory.Read<PCX>(mapperManager.GetPCXFilePath(inputSettings), inputContext).VGAPalette.Select(x => new RGB666Color(x.Red, x.Green, x.Blue)).ToArray() //}; // Set tiles while keeping track of the size changes var prevTileSize = outLev.MapData.Tiles.Length * 6; outLev.MapData.Tiles = inputMap.MapTiles.Select(x => x.Data).ToArray(); var newTileSize = outLev.MapData.Tiles.Length * 6; // Update pointers outLev.EventBlockPointer += newTileSize - prevTileSize; outLev.TextureBlockPointer += newTileSize - prevTileSize; // TODO: Set tilemap from .pcx file //outLev.TileTextureData = null; // Get the file tables var outputDesNames = FileFactory.Read <R1_PC_WorldFile>(rdManager.GetWorldFilePath(outputSettings), outputContext).DESFileNames; var outputEtaNames = FileFactory.Read <R1_PC_WorldFile>(rdManager.GetWorldFilePath(outputSettings), outputContext).ETAFileNames; // Set event data outLev.EventData.EventCount = (ushort)inputLev.EventData.Count; outLev.EventData.EventLinkingTable = inputObjManager.LinkTable; outLev.EventData.Events = inputLev.EventData.Cast <Unity_Object_R1>().Select(x => { var e = x.EventData; var newDesIndex = (uint)outputDesNames.FindItemIndex(y => y == inputObjManager.DES[x.DESIndex].Name); var newEtaIndex = (uint)outputEtaNames.FindItemIndex(y => y == inputObjManager.ETA[x.ETAIndex].Name); // Set DES and ETA indexes e.PC_ImageDescriptorsIndex = newDesIndex; e.PC_ImageBufferIndex = newDesIndex; e.PC_AnimationDescriptorsIndex = newDesIndex; e.PC_ETAIndex = newEtaIndex; // Set image and animation descriptor counts e.ImageDescriptorCount = (ushort)outputObjManager.DES[x.DESIndex].Data.Graphics.Sprites.Count; e.AnimDescriptorCount = (byte)outputObjManager.DES[x.DESIndex].Data.Graphics.Animations.Count; return(e); }).ToArray(); // Set event commands outLev.EventData.EventCommands = inputLev.EventData.Cast <Unity_Object_R1>().Select(x => { // Get the commands in the compiled format var cmds = EventCommandCompiler.Compile(x.EventData.Commands, x.EventData.Commands.ToBytes(inputSettings)); // Remove commands which only contain the invalid command if (cmds.Commands.Commands.Length == 1) { cmds = new EventCommandCompiler.CompiledEventCommandData(new R1_EventCommandCollection() { Commands = new R1_EventCommand[0] }, new ushort[0]); } // Create a command object return(new R1_PC_EventCommand { CommandLength = (ushort)cmds.Commands.Commands.Select(y => y.Length).Sum(), Commands = cmds.Commands, LabelOffsetCount = (ushort)cmds.LabelOffsets.Length, LabelOffsetTable = cmds.LabelOffsets }); }).ToArray(); // TODO: Get data from .ini file // Set profile define data outLev.ProfileDefine = new R1_PC_ProfileDefine { LevelName = "Test Export", LevelAuthor = "RayCarrot", LevelDescription = "This is a test export map", Power_Fist = true, Power_Hang = true, Power_Run = true, Power_Seed = false, Power_Helico = true, Power_SuperHelico = false }; // Write the changes to the file FileFactory.Write <R1_PC_LevFile>(rdManager.GetLevelFilePath(outputSettings), outputContext); } } }
public async UniTask ExportMusicAsync(GameSettings settings, string outputPath) { using (var context = new Context(settings)) { var s = context.Deserializer; void ExportSampleSigned(string directory, string filename, sbyte[] data, uint sampleRate, ushort channels) { // Create the directory Directory.CreateDirectory(directory); byte[] unsignedData = data.Select(b => (byte)(b + 128)).ToArray(); // Create WAV data var formatChunk = new WAVFormatChunk() { ChunkHeader = "fmt ", FormatType = 1, ChannelCount = channels, SampleRate = sampleRate, BitsPerSample = 8, }; var wav = new WAV { Magic = "RIFF", FileTypeHeader = "WAVE", Chunks = new WAVChunk[] { formatChunk, new WAVChunk() { ChunkHeader = "data", Data = unsignedData } } }; formatChunk.ByteRate = (formatChunk.SampleRate * formatChunk.BitsPerSample * formatChunk.ChannelCount) / 8; formatChunk.BlockAlign = (ushort)((formatChunk.BitsPerSample * formatChunk.ChannelCount) / 8); // Get the output path var outputFilePath = Path.Combine(directory, filename + ".wav"); // Create and open the output file using (var outputStream = File.Create(outputFilePath)) { // Create a context using (var wavContext = new Context(settings)) { // Create a key const string wavKey = "wav"; // Add the file to the context wavContext.AddFile(new StreamFile(wavKey, outputStream, wavContext)); // Write the data FileFactory.Write <WAV>(wavKey, wav, wavContext); } } } await LoadFilesAsync(context); Pointer ptr = context.FilePointer(GetROMFilePath); var pointerTable = PointerTables.GBAIsometric_PointerTable(s.GameSettings.GameModeSelection, ptr.file); MusyX_File musyxFile = null; s.DoAt(pointerTable[GBAIsometric_Pointer.MusyxFile], () => { musyxFile = s.SerializeObject <MusyX_File>(musyxFile, name: nameof(musyxFile)); }); string outPath = outputPath + "/Sounds/"; for (int i = 0; i < musyxFile.SampleTable.Value.Samples.Length; i++) { var e = musyxFile.SampleTable.Value.Samples[i].Value; //Util.ByteArrayToFile(outPath + $"{i}_{e.Offset.AbsoluteOffset:X8}.bin", e.SampleData); ExportSampleSigned(outPath, $"{i}_{musyxFile.SampleTable.Value.Samples[i].pointer.SerializedOffset:X8}", e.SampleData, e.SampleRate, 1); } outPath = outputPath + "/SongData/"; for (int i = 0; i < musyxFile.SongTable.Value.Length; i++) { var songBytes = musyxFile.SongTable.Value.SongBytes[i]; Util.ByteArrayToFile(outPath + $"{i}_{musyxFile.SongTable.Value.Songs[i].SerializedOffset:X8}.son", songBytes); } outPath = outputPath + "/InstrumentData/"; for (int i = 0; i < musyxFile.InstrumentTable.Value.Instruments.Length; i++) { var instrumentBytes = musyxFile.InstrumentTable.Value.InstrumentBytes[i]; Util.ByteArrayToFile(outPath + $"{i}_{musyxFile.InstrumentTable.Value.Instruments[i].SerializedOffset:X8}.bin", instrumentBytes); } } }