diff --git a/DS_Map/BuildingEditor.cs b/DS_Map/BuildingEditor.cs index 3988b65..24453ad 100644 --- a/DS_Map/BuildingEditor.cs +++ b/DS_Map/BuildingEditor.cs @@ -62,7 +62,7 @@ namespace DSPRE { string readingPath = folder + rom.GetBuildingModelsDirPath(interior) + "\\" + modelID.ToString("D4"); byte[] txFile = File.ReadAllBytes(readingPath); - byte[] texData = DSUtils.GetTexturesFromTexturedNSBMD(txFile); + byte[] texData = NSBUtils.GetTexturesFromTexturedNSBMD(txFile); if (texData.Length <= 4) { Console.WriteLine("No textures found"); @@ -76,7 +76,7 @@ namespace DSPRE { string filePath = folder + rom.GetBuildingModelsDirPath(interior) + "\\" + currentIndex.ToString("D4"); using (DSUtils.EasyReader reader = new DSUtils.EasyReader(filePath, 0x14)) { - string nsbmdName = DSUtils.ReadNSBMDname(reader); + string nsbmdName = NSBUtils.ReadNSBMDname(reader); buildingEditorBldListBox.Items.Add("[" + currentIndex.ToString("D3") + "] " + nsbmdName); } } @@ -207,7 +207,7 @@ namespace DSPRE { int currentIndex = buildingEditorBldListBox.SelectedIndex; File.Copy(im.FileName, folder + rom.GetBuildingModelsDirPath(interiorCheckBox.Checked) + "\\" + currentIndex.ToString("D4"), true); - buildingEditorBldListBox.Items[currentIndex] = "[" + currentIndex.ToString("D3") + "] " + DSUtils.ReadNSBMDname(reader, 0x14); + buildingEditorBldListBox.Items[currentIndex] = "[" + currentIndex.ToString("D3") + "] " + NSBUtils.ReadNSBMDname(reader, 0x14); buildingEditorListBox_SelectedIndexChanged(null, null); } } diff --git a/DS_Map/DSPRE.csproj b/DS_Map/DSPRE.csproj index b568f9f..67a1782 100644 --- a/DS_Map/DSPRE.csproj +++ b/DS_Map/DSPRE.csproj @@ -136,6 +136,8 @@ LearnsetEditor.cs + + Form diff --git a/DS_Map/DSUtils.cs b/DS_Map/DSUtils.cs index 882e741..003f189 100644 --- a/DS_Map/DSUtils.cs +++ b/DS_Map/DSUtils.cs @@ -16,6 +16,12 @@ using static DSPRE.RomInfo; namespace DSPRE { public static class DSUtils { + + public const int ERR_OVERLAY_NOTFOUND = -1; + public const int ERR_OVERLAY_ALREADY_UNCOMPRESSED = -2; + + public const string backupSuffix = ".backup"; + public static readonly string NDSRomFilter = "NDS File (*.nds)|*.nds"; public class EasyReader : BinaryReader { public EasyReader(string path, long pos = 0) : base(File.OpenRead(path)) { @@ -31,28 +37,6 @@ namespace DSPRE { } } - public const int NSBMD_DOESNTHAVE_TEXTURE = 0; - public const int NSBMD_HAS_TEXTURE = 1; - - public const int ERR_OVERLAY_NOTFOUND = -1; - public const int ERR_OVERLAY_ALREADY_UNCOMPRESSED = -2; - - public const string backupSuffix = ".backup"; - - public static string ReadNSBMDname(BinaryReader reader, long? startPos = null) { - if (startPos != null) { - reader.BaseStream.Position = (long)startPos; - } - - if (reader.ReadUInt32() == NSBMD.NDS_TYPE_MDL0) { //MDL0 - reader.BaseStream.Position += 0x1c; - } else { - reader.BaseStream.Position += 0x1c + 4; - } - - return Encoding.UTF8.GetString(reader.ReadBytes(16)); - } - public static void ModelToDAE(string modelName, byte[] modelData, byte[] textureData) { MessageBox.Show("Choose output folder.\nDSPRE will automatically create a sub-folder in it.", "Awaiting user input", MessageBoxButtons.OK, MessageBoxIcon.Information); @@ -82,14 +66,14 @@ namespace DSPRE { string tempNSBMDPath = outDir + "_temp.nsbmd"; if (textureData != null && textureData.Length > 0) { - modelData = DSUtils.BuildNSBMDwithTextures(modelData, textureData); + modelData = NSBUtils.BuildNSBMDwithTextures(modelData, textureData); } File.WriteAllBytes(tempNSBMDPath, modelData); /* Check correct creation of temp NSBMD file*/ if (!File.Exists(tempNSBMDPath)) { - MessageBox.Show("NSBMD file corresponding to this map could not be found.\nAborting", "Error", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show("Expected NSBMD file could not be found.\nAborting", "Error", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } @@ -147,7 +131,7 @@ namespace DSPRE { string tempNSBMDPath = outDir + "_temp.nsbmd"; if (textureData != null && textureData.Length > 0) { - modelData = DSUtils.BuildNSBMDwithTextures(modelData, textureData); + modelData = NSBUtils.BuildNSBMDwithTextures(modelData, textureData); } File.WriteAllBytes(tempNSBMDPath, modelData); @@ -219,29 +203,6 @@ namespace DSPRE { } return buffer; } - public static int DecompressOverlay(string overlayFilePath, bool makeBackup = true) { - if (!File.Exists(overlayFilePath)) { - MessageBox.Show($"File to decompress \"{overlayFilePath}\" doesn't exist", - "Overlay not found", MessageBoxButtons.OK, MessageBoxIcon.Error); - return ERR_OVERLAY_NOTFOUND; - } - - if (makeBackup) { - if (File.Exists(overlayFilePath + backupSuffix)) { - File.Delete(overlayFilePath + backupSuffix); - } - File.Copy(overlayFilePath, overlayFilePath + backupSuffix); - } - - Process decompress = DSUtils.CreateDecompressProcess(overlayFilePath); - decompress.Start(); - decompress.WaitForExit(); - return decompress.ExitCode; - } - public static int DecompressOverlay(int overlayNumber, bool makeBackup = true) { - return DecompressOverlay(GetOverlayPath(overlayNumber), makeBackup); - } - public static Process CreateDecompressProcess(string path) { Process decompress = new Process(); decompress.StartInfo.FileName = @"Tools\blz.exe"; @@ -252,87 +213,6 @@ namespace DSPRE { } - public static int CompressOverlay(int overlayNumber) { - string overlayFilePath = GetOverlayPath(overlayNumber); - - if (!File.Exists(overlayFilePath)) { - MessageBox.Show("Overlay to decompress #" + overlayNumber + " doesn't exist", - "Overlay not found", MessageBoxButtons.OK, MessageBoxIcon.Error); - return ERR_OVERLAY_NOTFOUND; - } - - Process compress = new Process(); - compress.StartInfo.FileName = @"Tools\blz.exe"; - compress.StartInfo.Arguments = "-en " + '"' + overlayFilePath + '"'; - Application.DoEvents(); - compress.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; - compress.StartInfo.CreateNoWindow = true; - compress.Start(); - compress.WaitForExit(); - return compress.ExitCode; - } - public static string GetOverlayPath(int overlayNumber) { - return $"{workDir}overlay\\overlay_{overlayNumber:D4}.bin"; - } - public static void RestoreOverlayFromCompressedBackup(int overlayNumber, bool eventEditorIsReady) { - String overlayFilePath = GetOverlayPath(overlayNumber); - - if (File.Exists(overlayFilePath + backupSuffix)) { - if (new FileInfo(overlayFilePath).Length <= new FileInfo(overlayFilePath + backupSuffix).Length) { //if overlay is bigger than its backup - Console.WriteLine("Overlay " + overlayNumber + " is already compressed."); - return; - } else { - File.Delete(overlayFilePath); - File.Move(overlayFilePath + backupSuffix, overlayFilePath); - } - } else { - string msg = "Overlay File " + '"' + overlayFilePath + backupSuffix + '"' + " couldn't be found and restored."; - Console.WriteLine(msg); - - if (eventEditorIsReady) { - MessageBox.Show(msg, "Can't restore overlay from backup", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - } - - /** - * Only checks if the overlay is CONFIGURED as compressed - **/ - public static bool CheckOverlayHasCompressionFlag(int ovNumber) { - using (BinaryReader f = new BinaryReader(File.OpenRead(RomInfo.overlayTablePath))) { - f.BaseStream.Position = ovNumber * 32 + 31; //overlayNumber * size of entry + offset - return f.ReadByte() % 2 != 0; - } - } - - /** - * Checks the actual size of the overlay file - **/ - public static bool OverlayIsCompressed(int ovNumber) { - return (new FileInfo(GetOverlayPath(ovNumber)).Length < GetOverlayUncompressedSize(ovNumber)); - } - public static uint GetOverlayUncompressedSize(int ovNumber) { - using (BinaryReader f = new BinaryReader(File.OpenRead(RomInfo.overlayTablePath))) { - f.BaseStream.Position = ovNumber * 32 + 8; //overlayNumber * size of entry + offset - return f.ReadUInt32(); - } - } - public static uint GetOverlayRAMAddress(int ovNumber) { - using (BinaryReader f = new BinaryReader(File.OpenRead(RomInfo.overlayTablePath))) { - f.BaseStream.Position = ovNumber * 32 + 4; //overlayNumber * size of entry + offset - return f.ReadUInt32(); - } - } - public static void SetOverlayCompressionInTable(int ovNumber, byte compressStatus) { - if (compressStatus < 0 || compressStatus > 3) { - Console.WriteLine("Compression status " + compressStatus + " is invalid. No operation performed."); - return; - } - using (BinaryWriter f = new BinaryWriter(File.OpenWrite(RomInfo.overlayTablePath))) { - f.BaseStream.Position = ovNumber * 32 + 31; //overlayNumber * size of entry + offset - f.Write(compressStatus); - } - } public static void RepackROM(string ndsFileName) { Process repack = new Process(); repack.StartInfo.FileName = @"Tools\ndstool.exe"; @@ -413,122 +293,6 @@ namespace DSPRE { }); } - public static byte[] GetModelWithoutTextures(byte[] modelFile) { - byte[] nsbmdHeaderData; - uint mdl0Size; - byte[] mdl0Data; - - using (BinaryReader modelReader = new BinaryReader(new MemoryStream(modelFile))) { - modelReader.BaseStream.Position = 0x0; - nsbmdHeaderData = modelReader.ReadBytes(0x8); - - modelReader.BaseStream.Position = 0x1C; - mdl0Size = modelReader.ReadUInt32(); // Read mdl0 file size - - modelReader.BaseStream.Position = 0x18; - mdl0Data = modelReader.ReadBytes((int)mdl0Size); - } - - MemoryStream output = new MemoryStream(); - using (BinaryWriter writer = new BinaryWriter(output)) { - - writer.Write(nsbmdHeaderData); // Write first header bytes, same for all NSBMD. - writer.Write(mdl0Size + 0x14); - writer.Write((short)0x10); // Writes BMD0 header size (always 16) - writer.Write((short)0x1); // Write new number of sub-files, since embedded textures are removed - writer.Write((uint)0x14); // Writes new start offset of MDL0 - - writer.Write(mdl0Data); // Writes MDL0; - } - return output.ToArray(); - } - - public static byte[] GetTexturesFromTexturedNSBMD(byte[] modelFile) { - using (BinaryReader byteArrReader = new BinaryReader(new MemoryStream(modelFile))) { - byteArrReader.BaseStream.Position = 14; - if (byteArrReader.ReadUInt16() < 2) //No textures - return new byte[0]; - - byteArrReader.BaseStream.Position = 20; - int texAbsoluteOffset = byteArrReader.ReadInt32(); - - byteArrReader.BaseStream.Position = texAbsoluteOffset + 4; - uint textureSize = byteArrReader.ReadUInt32(); - - byte[] nsbtxHeader = DSUtils.BuildNSBTXHeader(20 + textureSize); - byte[] texData = DSUtils.ReadFromByteArray(modelFile, readFrom: texAbsoluteOffset); - - byte[] output = new byte[nsbtxHeader.Length + texData.Length]; - Buffer.BlockCopy(nsbtxHeader, 0, output, 0, nsbtxHeader.Length); - Buffer.BlockCopy(texData, 0, output, nsbtxHeader.Length, texData.Length); - return output; - } - } - public static int CheckNSBMDHeader(byte[] modelFile) { - using (BinaryReader byteArrReader = new BinaryReader(new MemoryStream(modelFile))) { - if (byteArrReader.ReadUInt32() != NSBMD.NDS_TYPE_BMD0) { - MessageBox.Show("Please select an NSBMD file.", "Invalid File", MessageBoxButtons.OK, MessageBoxIcon.Error); - return -1; - } - - byteArrReader.BaseStream.Position = 0xE; - return byteArrReader.ReadInt16() >= 2 ? NSBMD_HAS_TEXTURE : NSBMD_DOESNTHAVE_TEXTURE; - } - } - public static byte[] BuildNSBTXHeader(uint texturesSize) { - MemoryStream ms = new MemoryStream(); - - using (BinaryWriter bw = new BinaryWriter(ms)) { - bw.Write(Encoding.UTF8.GetBytes("BTX0")); // Write magic code BTX0 - bw.Write((ushort)0xFEFF); // Byte order - bw.Write((ushort)0x0001); // ??? - bw.Write(texturesSize); // Write size of textures block - bw.Write((short)0x10); //Header size - bw.Write((short)0x01); //Number of sub-files??? - bw.Write((uint)0x14); // Offset to sub-file - } - return ms.ToArray(); - } - public static byte[] BuildNSBMDwithTextures(byte[] nsbmd, byte[] nsbtx) { - byte[] wholeMDL0 = GetFirstBlock(nsbmd); - byte[] wholeTEX0 = GetFirstBlock(nsbtx); - - MemoryStream ms = new MemoryStream(); - using (BinaryWriter msWriter = new BinaryWriter(ms)) { - msWriter.Write(NSBMD.NDS_TYPE_BMD0); - msWriter.Write(NSBMD.NDS_TYPE_BYTEORDER); - msWriter.Write(NSBMD.NDS_TYPE_UNK2); - - ushort nBlocks = 2; - uint modelLength = (uint)(wholeMDL0.Length + NSBMD.HEADERSIZE + 4 * nBlocks); - msWriter.Write((uint)(modelLength + wholeTEX0.Length)); - msWriter.Write(NSBMD.HEADERSIZE); //Header size, always 16 - msWriter.Write(nBlocks); //Number of blocks, now it's 2 because we are inserting textures - - msWriter.Write((uint)(msWriter.BaseStream.Position + 4 * nBlocks)); //Absolute offset to model data. We are gonna have to write two offsets - - msWriter.Write(modelLength); //Copy offset to TEX0 - msWriter.Write(wholeMDL0); - msWriter.Write(wholeTEX0); - } - return ms.ToArray(); - } - private static byte[] GetFirstBlock(byte[] NSBFile) { - int blockSize; - uint offsetToMainBlock; - using (BinaryReader reader = new BinaryReader(new MemoryStream(NSBFile))) { - reader.BaseStream.Position = 16; - offsetToMainBlock = reader.ReadUInt32(); - - reader.BaseStream.Position = offsetToMainBlock + 4; - blockSize = reader.ReadInt32(); - } - byte[] blockData = new byte[blockSize]; - Buffer.BlockCopy(NSBFile, (int)offsetToMainBlock, blockData, 0, blockSize); - - return blockData; - } - public static Image GetPokePic(int species, int w, int h) { PaletteBase paletteBase; bool fiveDigits = false; // some extreme future proofing @@ -547,10 +311,10 @@ namespace DSPRE { string iconTablePath; int iconPalTableOffsetFromFileStart; - string ov129path = DSUtils.GetOverlayPath(129); + string ov129path = OverlayUtils.GetPath(129); if (File.Exists(ov129path)) { // if overlay 129 exists, read it from there - iconPalTableOffsetFromFileStart = (int)(RomInfo.monIconPalTableAddress - DSUtils.GetOverlayRAMAddress(129)); + iconPalTableOffsetFromFileStart = (int)(RomInfo.monIconPalTableAddress - OverlayUtils.OverlayTable.GetRAMAddress(129)); iconTablePath = ov129path; } else if ((int)(RomInfo.monIconPalTableAddress - RomInfo.synthOverlayLoadAddress) >= 0) { // if there is a synthetic overlay, read it from there diff --git a/DS_Map/Main Window.cs b/DS_Map/Main Window.cs index a3c9b5c..f88a6d3 100644 --- a/DS_Map/Main Window.cs +++ b/DS_Map/Main Window.cs @@ -355,7 +355,7 @@ namespace DSPRE { } byte[] modelFile = DSUtils.ReadFromFile(of.FileName); - if (DSUtils.CheckNSBMDHeader(modelFile) == DSUtils.NSBMD_DOESNTHAVE_TEXTURE) { + if (NSBUtils.CheckNSBMDHeader(modelFile) == NSBUtils.NSBMD_DOESNTHAVE_TEXTURE) { MessageBox.Show("This NSBMD file is untextured.", "No textures to extract", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } @@ -370,7 +370,7 @@ namespace DSPRE { return; } - DSUtils.WriteToFile(texSf.FileName, DSUtils.GetTexturesFromTexturedNSBMD(modelFile)); + DSUtils.WriteToFile(texSf.FileName, NSBUtils.GetTexturesFromTexturedNSBMD(modelFile)); MessageBox.Show("The textures of " + of.FileName + " have been extracted and saved.", "Textures saved", MessageBoxButtons.OK, MessageBoxIcon.Information); } @@ -384,7 +384,7 @@ namespace DSPRE { } byte[] modelFile = DSUtils.ReadFromFile(of.FileName); - if (DSUtils.CheckNSBMDHeader(modelFile) == DSUtils.NSBMD_DOESNTHAVE_TEXTURE) { + if (NSBUtils.CheckNSBMDHeader(modelFile) == NSBUtils.NSBMD_DOESNTHAVE_TEXTURE) { MessageBox.Show("This NSBMD file is already untextured.", "No textures to remove", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } @@ -400,7 +400,7 @@ namespace DSPRE { }; if (texSf.ShowDialog() == DialogResult.OK) { - DSUtils.WriteToFile(texSf.FileName, DSUtils.GetTexturesFromTexturedNSBMD(modelFile)); + DSUtils.WriteToFile(texSf.FileName, NSBUtils.GetTexturesFromTexturedNSBMD(modelFile)); extramsg = " exported and"; } } @@ -415,7 +415,7 @@ namespace DSPRE { return; } - DSUtils.WriteToFile(sf.FileName, DSUtils.GetModelWithoutTextures(modelFile)); + DSUtils.WriteToFile(sf.FileName, NSBUtils.GetModelWithoutTextures(modelFile)); MessageBox.Show("Textures correctly" + extramsg + " removed!", "Success!", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void nsbmdAddTexButton_Click(object sender, EventArgs e) { @@ -426,7 +426,7 @@ namespace DSPRE { return; byte[] modelFile = File.ReadAllBytes(of.FileName); - if (DSUtils.CheckNSBMDHeader(modelFile) == DSUtils.NSBMD_HAS_TEXTURE) { + if (NSBUtils.CheckNSBMDHeader(modelFile) == NSBUtils.NSBMD_HAS_TEXTURE) { DialogResult d = MessageBox.Show("This NSBMD file is already textured.\nDo you want to overwrite its textures?", "Textures found", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (d.Equals(DialogResult.No)) { return; @@ -461,7 +461,7 @@ namespace DSPRE { if (sf.ShowDialog(this) != DialogResult.OK) return; - DSUtils.WriteToFile(sf.FileName, DSUtils.BuildNSBMDwithTextures(modelFile, textureFile), fmode: FileMode.Create); + DSUtils.WriteToFile(sf.FileName, NSBUtils.BuildNSBMDwithTextures(modelFile, textureFile), fmode: FileMode.Create); MessageBox.Show("Textures correctly written to NSBMD file.", "Success!", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void OpenCommandsDatabase(Dictionary namesDict, Dictionary paramsDict, Dictionary actionsDict, @@ -730,19 +730,19 @@ namespace DSPRE { statusLabelMessage("Repacking ROM..."); - if (DSUtils.CheckOverlayHasCompressionFlag(1)) { + if (OverlayUtils.OverlayTable.IsDefaultCompressed(1)) { if (PatchToolboxDialog.overlay1MustBeRestoredFromBackup) { - DSUtils.RestoreOverlayFromCompressedBackup(1, eventEditorIsReady); + OverlayUtils.RestoreFromCompressedBackup(1, eventEditorIsReady); } else { - if (!DSUtils.OverlayIsCompressed(1)) { - DSUtils.CompressOverlay(1); + if (!OverlayUtils.IsCompressed(1)) { + OverlayUtils.Compress(1); } } } - if (DSUtils.CheckOverlayHasCompressionFlag(RomInfo.initialMoneyOverlayNumber)) { - if (!DSUtils.OverlayIsCompressed(RomInfo.initialMoneyOverlayNumber)) { - DSUtils.CompressOverlay(RomInfo.initialMoneyOverlayNumber); + if (OverlayUtils.OverlayTable.IsDefaultCompressed(RomInfo.initialMoneyOverlayNumber)) { + if (!OverlayUtils.IsCompressed(RomInfo.initialMoneyOverlayNumber)) { + OverlayUtils.Compress(RomInfo.initialMoneyOverlayNumber); } } @@ -753,8 +753,8 @@ namespace DSPRE { if (RomInfo.gameFamily != gFamEnum.DP && RomInfo.gameFamily != gFamEnum.Plat) { if (eventEditorIsReady) { - if (DSUtils.OverlayIsCompressed(1)) { - DSUtils.DecompressOverlay(1); + if (OverlayUtils.IsCompressed(1)) { + OverlayUtils.Decompress(1); } } } @@ -3237,7 +3237,7 @@ namespace DSPRE { }; reader.BaseStream.Position += 0x14; - selectMapComboBox.Items.Add(i.ToString("D3") + MapHeader.nameSeparator + DSUtils.ReadNSBMDname(reader)); + selectMapComboBox.Items.Add(i.ToString("D3") + MapHeader.nameSeparator + NSBUtils.ReadNSBMDname(reader)); } } @@ -4681,7 +4681,7 @@ namespace DSPRE { string texturePath = RomInfo.gameDirs[DirNames.mapTextures].unpackedDir + "\\" + (mapTextureComboBox.SelectedIndex - 1).ToString("D4"); byte[] texturesToEmbed = File.ReadAllBytes(texturePath); - modelToWrite = DSUtils.BuildNSBMDwithTextures(currentMapFile.mapModelData, texturesToEmbed); + modelToWrite = NSBUtils.BuildNSBMDwithTextures(currentMapFile.mapModelData, texturesToEmbed); } else { /* Untextured NSBMD file */ em.Filter = MapFile.UntexturedNSBMDFilter; if (em.ShowDialog(this) != DialogResult.OK) { @@ -5315,9 +5315,9 @@ namespace DSPRE { break; default: // HGSS Overlay 1 must be decompressed in order to read the overworld table - if (DSUtils.CheckOverlayHasCompressionFlag(1)) { - if (DSUtils.OverlayIsCompressed(1)) { - if (DSUtils.DecompressOverlay(1) < 0) { + if (OverlayUtils.OverlayTable.IsDefaultCompressed(1)) { + if (OverlayUtils.IsCompressed(1)) { + if (OverlayUtils.Decompress(1) < 0) { MessageBox.Show("Overlay 1 couldn't be decompressed.\nOverworld sprites in the Event Editor will be " + "displayed incorrectly or not displayed at all.", "Unexpected EOF", MessageBoxButtons.OK, MessageBoxIcon.Error); } @@ -8500,7 +8500,7 @@ namespace DSPRE { RomInfo.PrepareCameraData(); cameraEditorDataGridView.Rows.Clear(); - if (DSUtils.CheckOverlayHasCompressionFlag(RomInfo.cameraTblOverlayNumber)) { + if (OverlayUtils.OverlayTable.IsDefaultCompressed(RomInfo.cameraTblOverlayNumber)) { DialogResult d1 = MessageBox.Show("It is STRONGLY recommended to configure Overlay1 as uncompressed before proceeding.\n\n" + "More details in the following dialog.\n\n" + "Do you want to know more?", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); @@ -8513,15 +8513,15 @@ namespace DSPRE { "If you change your mind, you can apply it later by accessing the Patch Toolbox.", "Caution", MessageBoxButtons.OK, MessageBoxIcon.Information); - if (DSUtils.OverlayIsCompressed(RomInfo.cameraTblOverlayNumber)) { - DSUtils.DecompressOverlay(RomInfo.cameraTblOverlayNumber); + if (OverlayUtils.IsCompressed(RomInfo.cameraTblOverlayNumber)) { + OverlayUtils.Decompress(RomInfo.cameraTblOverlayNumber); } } } uint[] RAMaddresses = new uint[RomInfo.cameraTblOffsetsToRAMaddress.Length]; - string camOverlayPath = DSUtils.GetOverlayPath(RomInfo.cameraTblOverlayNumber); + string camOverlayPath = OverlayUtils.GetPath(RomInfo.cameraTblOverlayNumber); using (DSUtils.EasyReader br = new DSUtils.EasyReader(camOverlayPath)) { for (int i = 0; i < RomInfo.cameraTblOffsetsToRAMaddress.Length; i++) { br.BaseStream.Position = RomInfo.cameraTblOffsetsToRAMaddress[i]; @@ -8538,7 +8538,7 @@ namespace DSPRE { } } - overlayCameraTblOffset = RAMaddresses[0] - DSUtils.GetOverlayRAMAddress(RomInfo.cameraTblOverlayNumber); + overlayCameraTblOffset = RAMaddresses[0] - OverlayUtils.OverlayTable.GetRAMAddress(RomInfo.cameraTblOverlayNumber); using (DSUtils.EasyReader br = new DSUtils.EasyReader(camOverlayPath, overlayCameraTblOffset)) { if (RomInfo.gameFamily == gFamEnum.HGSS) { currentCameraTable = new GameCamera[17]; @@ -8568,7 +8568,7 @@ namespace DSPRE { } } private void saveCameraTableButton_Click(object sender, EventArgs e) { - SaveCameraTable(DSUtils.GetOverlayPath(RomInfo.cameraTblOverlayNumber), overlayCameraTblOffset); + SaveCameraTable(OverlayUtils.GetPath(RomInfo.cameraTblOverlayNumber), overlayCameraTblOffset); } private void cameraEditorDataGridView_CellValidated(object sender, DataGridViewCellEventArgs e) { currentCameraTable[e.RowIndex][e.ColumnIndex] = cameraEditorDataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value; diff --git a/DS_Map/NSBUtils.cs b/DS_Map/NSBUtils.cs new file mode 100644 index 0000000..3026d8e --- /dev/null +++ b/DS_Map/NSBUtils.cs @@ -0,0 +1,142 @@ +using LibNDSFormats.NSBMD; +using System; +using System.IO; +using System.Text; +using System.Windows.Forms; + +namespace DSPRE { + public static class NSBUtils { + public const int NSBMD_DOESNTHAVE_TEXTURE = 0; + public const int NSBMD_HAS_TEXTURE = 1; + + public static string ReadNSBMDname(BinaryReader reader, long? startPos = null) { + if (startPos != null) { + reader.BaseStream.Position = (long)startPos; + } + + if (reader.ReadUInt32() == NSBMD.NDS_TYPE_MDL0) { //MDL0 + reader.BaseStream.Position += 0x1c; + } else { + reader.BaseStream.Position += 0x1c + 4; + } + + return Encoding.UTF8.GetString(reader.ReadBytes(16)); + } + public static byte[] BuildNSBMDwithTextures(byte[] nsbmd, byte[] nsbtx) { + byte[] wholeMDL0 = GetFirstBlock(nsbmd); + byte[] wholeTEX0 = GetFirstBlock(nsbtx); + + MemoryStream ms = new MemoryStream(); + using (BinaryWriter msWriter = new BinaryWriter(ms)) { + msWriter.Write(NSBMD.NDS_TYPE_BMD0); + msWriter.Write(NSBMD.NDS_TYPE_BYTEORDER); + msWriter.Write(NSBMD.NDS_TYPE_UNK2); + + ushort nBlocks = 2; + uint modelLength = (uint)(wholeMDL0.Length + NSBMD.HEADERSIZE + 4 * nBlocks); + msWriter.Write((uint)(modelLength + wholeTEX0.Length)); + msWriter.Write(NSBMD.HEADERSIZE); //Header size, always 16 + msWriter.Write(nBlocks); //Number of blocks, now it's 2 because we are inserting textures + + msWriter.Write((uint)(msWriter.BaseStream.Position + 4 * nBlocks)); //Absolute offset to model data. We are gonna have to write two offsets + + msWriter.Write(modelLength); //Copy offset to TEX0 + msWriter.Write(wholeMDL0); + msWriter.Write(wholeTEX0); + } + return ms.ToArray(); + } + public static byte[] BuildNSBTXHeader(uint texturesSize) { + MemoryStream ms = new MemoryStream(); + + using (BinaryWriter bw = new BinaryWriter(ms)) { + bw.Write(Encoding.UTF8.GetBytes("BTX0")); // Write magic code BTX0 + bw.Write((ushort)0xFEFF); // Byte order + bw.Write((ushort)0x0001); // ??? + bw.Write(texturesSize); // Write size of textures block + bw.Write((short)0x10); //Header size + bw.Write((short)0x01); //Number of sub-files??? + bw.Write((uint)0x14); // Offset to sub-file + } + return ms.ToArray(); + } + public static int CheckNSBMDHeader(byte[] modelFile) { + using (BinaryReader byteArrReader = new BinaryReader(new MemoryStream(modelFile))) { + if (byteArrReader.ReadUInt32() != NSBMD.NDS_TYPE_BMD0) { + MessageBox.Show("Please select an NSBMD file.", "Invalid File", MessageBoxButtons.OK, MessageBoxIcon.Error); + return -1; + } + + byteArrReader.BaseStream.Position = 0xE; + return byteArrReader.ReadInt16() >= 2 ? NSBMD_HAS_TEXTURE : NSBMD_DOESNTHAVE_TEXTURE; + } + } + + public static byte[] GetModelWithoutTextures(byte[] modelFile) { + byte[] nsbmdHeaderData; + uint mdl0Size; + byte[] mdl0Data; + + using (BinaryReader modelReader = new BinaryReader(new MemoryStream(modelFile))) { + modelReader.BaseStream.Position = 0x0; + nsbmdHeaderData = modelReader.ReadBytes(0x8); + + modelReader.BaseStream.Position = 0x1C; + mdl0Size = modelReader.ReadUInt32(); // Read mdl0 file size + + modelReader.BaseStream.Position = 0x18; + mdl0Data = modelReader.ReadBytes((int)mdl0Size); + } + + MemoryStream output = new MemoryStream(); + using (BinaryWriter writer = new BinaryWriter(output)) { + + writer.Write(nsbmdHeaderData); // Write first header bytes, same for all NSBMD. + writer.Write(mdl0Size + 0x14); + writer.Write((short)0x10); // Writes BMD0 header size (always 16) + writer.Write((short)0x1); // Write new number of sub-files, since embedded textures are removed + writer.Write((uint)0x14); // Writes new start offset of MDL0 + + writer.Write(mdl0Data); // Writes MDL0; + } + return output.ToArray(); + } + + public static byte[] GetTexturesFromTexturedNSBMD(byte[] modelFile) { + using (BinaryReader byteArrReader = new BinaryReader(new MemoryStream(modelFile))) { + byteArrReader.BaseStream.Position = 14; + if (byteArrReader.ReadUInt16() < 2) //No textures + return new byte[0]; + + byteArrReader.BaseStream.Position = 20; + int texAbsoluteOffset = byteArrReader.ReadInt32(); + + byteArrReader.BaseStream.Position = texAbsoluteOffset + 4; + uint textureSize = byteArrReader.ReadUInt32(); + + byte[] nsbtxHeader = NSBUtils.BuildNSBTXHeader(20 + textureSize); + byte[] texData = DSUtils.ReadFromByteArray(modelFile, readFrom: texAbsoluteOffset); + + byte[] output = new byte[nsbtxHeader.Length + texData.Length]; + Buffer.BlockCopy(nsbtxHeader, 0, output, 0, nsbtxHeader.Length); + Buffer.BlockCopy(texData, 0, output, nsbtxHeader.Length, texData.Length); + return output; + } + } + private static byte[] GetFirstBlock(byte[] NSBFile) { + int blockSize; + uint offsetToMainBlock; + using (BinaryReader reader = new BinaryReader(new MemoryStream(NSBFile))) { + reader.BaseStream.Position = 16; + offsetToMainBlock = reader.ReadUInt32(); + + reader.BaseStream.Position = offsetToMainBlock + 4; + blockSize = reader.ReadInt32(); + } + byte[] blockData = new byte[blockSize]; + Buffer.BlockCopy(NSBFile, (int)offsetToMainBlock, blockData, 0, blockSize); + + return blockData; + } + } +} \ No newline at end of file diff --git a/DS_Map/OverlayUtils.cs b/DS_Map/OverlayUtils.cs new file mode 100644 index 0000000..c2a598d --- /dev/null +++ b/DS_Map/OverlayUtils.cs @@ -0,0 +1,112 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Windows.Forms; +using static DSPRE.DSUtils; +using static DSPRE.RomInfo; + +namespace DSPRE { + public static class OverlayUtils { + public static class OverlayTable { + private const int ENTRY_LEN = 32; + + /** + * Only checks if the overlay is CONFIGURED as compressed + **/ + public static bool IsDefaultCompressed(int ovNumber) { + using (DSUtils.EasyReader f = new EasyReader(RomInfo.overlayTablePath, ovNumber * ENTRY_LEN + 31)) { + return (f.ReadByte() & 1) == 1; + } + } + public static void SetDefaultCompressed(int ovNumber, bool compressStatus) { + DSUtils.WriteToFile(RomInfo.overlayTablePath, new byte[] { compressStatus ? (byte)1 : (byte)0 }, (uint)(ovNumber * ENTRY_LEN + 31)); //overlayNumber * size of entry + offset + } + + public static uint GetRAMAddress(int ovNumber) { + using (DSUtils.EasyReader f = new EasyReader(RomInfo.overlayTablePath, ovNumber * ENTRY_LEN + 4)) { + return f.ReadUInt32(); + } + } + public static uint GetUncompressedSize(int ovNumber) { + using (DSUtils.EasyReader f = new EasyReader(RomInfo.overlayTablePath, ovNumber * ENTRY_LEN + 8)) { + return f.ReadUInt32(); + } + } + } + + + public static string GetPath(int overlayNumber) { + return $"{workDir}overlay\\overlay_{overlayNumber:D4}.bin"; + } + + /** + * Checks the actual size of the overlay file + **/ + public static bool IsCompressed(int ovNumber) { + return (new FileInfo(GetPath(ovNumber)).Length < OverlayTable.GetUncompressedSize(ovNumber)); + } + public static void RestoreFromCompressedBackup(int overlayNumber, bool eventEditorIsReady) { + String overlayFilePath = GetPath(overlayNumber); + + if (File.Exists(overlayFilePath + DSUtils.backupSuffix)) { + if (new FileInfo(overlayFilePath).Length <= new FileInfo(overlayFilePath + DSUtils.backupSuffix).Length) { //if overlay is bigger than its backup + Console.WriteLine($"Overlay {overlayNumber} is already compressed."); + return; + } else { + File.Delete(overlayFilePath); + File.Move(overlayFilePath + DSUtils.backupSuffix, overlayFilePath); + } + } else { + string msg = $"Overlay File {overlayFilePath}{DSUtils.backupSuffix} couldn't be found and restored."; + Console.WriteLine(msg); + + if (eventEditorIsReady) { + MessageBox.Show(msg, "Can't restore overlay from backup", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + public static int Compress(int overlayNumber) { + string overlayFilePath = GetPath(overlayNumber); + + if (!File.Exists(overlayFilePath)) { + MessageBox.Show("Overlay to decompress #" + overlayNumber + " doesn't exist", + "Overlay not found", MessageBoxButtons.OK, MessageBoxIcon.Error); + return ERR_OVERLAY_NOTFOUND; + } + + Process compress = new Process(); + compress.StartInfo.FileName = @"Tools\blz.exe"; + compress.StartInfo.Arguments = "-en " + '"' + overlayFilePath + '"'; + Application.DoEvents(); + compress.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + compress.StartInfo.CreateNoWindow = true; + compress.Start(); + compress.WaitForExit(); + return compress.ExitCode; + } + + public static int Decompress(string overlayFilePath, bool makeBackup = true) { + if (!File.Exists(overlayFilePath)) { + MessageBox.Show($"File to decompress \"{overlayFilePath}\" doesn't exist", + "Overlay not found", MessageBoxButtons.OK, MessageBoxIcon.Error); + return ERR_OVERLAY_NOTFOUND; + } + + if (makeBackup) { + if (File.Exists(overlayFilePath + backupSuffix)) { + File.Delete(overlayFilePath + backupSuffix); + } + File.Copy(overlayFilePath, overlayFilePath + backupSuffix); + } + + Process decompress = DSUtils.CreateDecompressProcess(overlayFilePath); + decompress.Start(); + decompress.WaitForExit(); + return decompress.ExitCode; + } + public static int Decompress(int overlayNumber, bool makeBackup = true) { + return Decompress(GetPath(overlayNumber), makeBackup); + } + + } +} \ No newline at end of file diff --git a/DS_Map/PatchToolboxDialog.cs b/DS_Map/PatchToolboxDialog.cs index 81c416c..4b7c50f 100644 --- a/DS_Map/PatchToolboxDialog.cs +++ b/DS_Map/PatchToolboxDialog.cs @@ -69,7 +69,7 @@ namespace DSPRE { CheckDynamicHeadersPatchApplied(); break; case gFamEnum.HGSS: - if (!DSUtils.CheckOverlayHasCompressionFlag(1)) { + if (!OverlayUtils.OverlayTable.IsDefaultCompressed(1)) { DisableOverlay1patch("Already applied"); overlay1CB.Visible = true; } @@ -174,8 +174,8 @@ namespace DSPRE { return false; } - string overlayFilePath = DSUtils.GetOverlayPath(data.overlayNumber); - DSUtils.DecompressOverlay(data.overlayNumber); + string overlayFilePath = OverlayUtils.GetPath(data.overlayNumber); + OverlayUtils.Decompress(data.overlayNumber); byte[] overlayCode1 = DSUtils.HexStringToByteArray(data.overlayString1); byte[] overlayCode1Read = DSUtils.ReadFromFile(overlayFilePath, data.overlayOffset1, overlayCode1.Length); @@ -365,7 +365,7 @@ namespace DSPRE { BDHCAMPatchData data = new BDHCAMPatchData(); if (RomInfo.gameFamily == gFamEnum.HGSS) { - if (DSUtils.CheckOverlayHasCompressionFlag(data.overlayNumber)) { + if (OverlayUtils.OverlayTable.IsDefaultCompressed(data.overlayNumber)) { DialogResult d1 = MessageBox.Show("It is STRONGLY recommended to configure Overlay1 as uncompressed before proceeding.\n\n" + "More details in the following dialog.\n\n" + "Do you want to know more?", "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); @@ -394,9 +394,9 @@ namespace DSPRE { ARM9.WriteBytes(DSUtils.HexStringToByteArray(data.branchString), data.branchOffset); //Write new branchOffset /* Write to overlayfile */ - string overlayFilePath = DSUtils.GetOverlayPath(data.overlayNumber); - if (DSUtils.OverlayIsCompressed(data.overlayNumber)) { - DSUtils.DecompressOverlay(data.overlayNumber); + string overlayFilePath = OverlayUtils.GetPath(data.overlayNumber); + if (OverlayUtils.IsCompressed(data.overlayNumber)) { + OverlayUtils.Decompress(data.overlayNumber); } DSUtils.WriteToFile(overlayFilePath, DSUtils.HexStringToByteArray(data.overlayString1), data.overlayOffset1); //Write new overlayCode1 @@ -434,7 +434,7 @@ namespace DSPRE { bool isCompressed = false; string stringDecompressOverlay = ""; - if (DSUtils.OverlayIsCompressed(1)) { + if (OverlayUtils.IsCompressed(1)) { isCompressed = true; stringDecompressOverlay = "- Overlay 1 will be decompressed.\n\n"; } @@ -446,9 +446,9 @@ namespace DSPRE { "Confirm to proceed", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (d == DialogResult.Yes) { - DSUtils.SetOverlayCompressionInTable(1, 0); + OverlayUtils.OverlayTable.SetDefaultCompressed(1, false); if (isCompressed) { - DSUtils.DecompressOverlay(1); + OverlayUtils.Decompress(1); } MessageBox.Show("Overlay1 is now configured as uncompressed.", "Operation successful", MessageBoxButtons.OK, MessageBoxIcon.Information); @@ -514,7 +514,7 @@ namespace DSPRE { //Distortion world - turnback cave Griseous Orb fix if (gameFamily.Equals(gFamEnum.Plat)) { - string ow9path = DSUtils.GetOverlayPath(9); + string ow9path = OverlayUtils.GetPath(9); int ow9offs = 0x8E20 + 10; int itemScriptID; diff --git a/DS_Map/ROMFiles/MapFile.cs b/DS_Map/ROMFiles/MapFile.cs index bced3aa..2919e76 100644 --- a/DS_Map/ROMFiles/MapFile.cs +++ b/DS_Map/ROMFiles/MapFile.cs @@ -185,7 +185,7 @@ namespace DSPRE.ROMFiles { modelReader.BaseStream.Position = 0xE; if (modelReader.ReadInt16() > 1) { // If NSBMD contains more than one segment, it means there are embedded textures we must remove - mapModelData = DSUtils.GetModelWithoutTextures(newData); + mapModelData = NSBUtils.GetModelWithoutTextures(newData); } else { modelReader.BaseStream.Position = 0x0; mapModelData = modelReader.ReadBytes((int)modelReader.BaseStream.Length); diff --git a/DS_Map/RomInfo.cs b/DS_Map/RomInfo.cs index bab3562..7ee44ca 100644 --- a/DS_Map/RomInfo.cs +++ b/DS_Map/RomInfo.cs @@ -467,7 +467,7 @@ namespace DSPRE { public static void SetOWtable() { switch (gameFamily) { case gFamEnum.DP: - OWtablePath = DSUtils.GetOverlayPath(5); + OWtablePath = OverlayUtils.GetPath(5); switch (gameLanguage) { // Go to the beginning of the overworld table case gLangEnum.English: OWTableOffset = 0x22BCC; @@ -481,7 +481,7 @@ namespace DSPRE { } break; case gFamEnum.Plat: - OWtablePath = DSUtils.GetOverlayPath(5); + OWtablePath = OverlayUtils.GetPath(5); switch (gameLanguage) { // Go to the beginning of the overworld table case gLangEnum.Italian: OWTableOffset = 0x2BC44; @@ -502,17 +502,17 @@ namespace DSPRE { } break; case gFamEnum.HGSS: - if (DSUtils.CheckOverlayHasCompressionFlag(1)) { - if (DSUtils.OverlayIsCompressed(1)) { - if (DSUtils.DecompressOverlay(1) < 0) { + if (OverlayUtils.OverlayTable.IsDefaultCompressed(1)) { + if (OverlayUtils.IsCompressed(1)) { + if (OverlayUtils.Decompress(1) < 0) { MessageBox.Show("Overlay 1 couldn't be decompressed.\nOverworld sprites in the Event Editor will be " + "displayed incorrectly or not displayed at all.", "Decompression error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } - string ov1Path = DSUtils.GetOverlayPath(1); - uint ov1Address = DSUtils.GetOverlayRAMAddress(1); + string ov1Path = OverlayUtils.GetPath(1); + uint ov1Address = OverlayUtils.OverlayTable.GetRAMAddress(1); int ramAddrOfPointer; switch (gameLanguage) { @@ -542,10 +542,10 @@ namespace DSPRE { return; } - string ov131path = DSUtils.GetOverlayPath(131); + string ov131path = OverlayUtils.GetPath(131); if (File.Exists(ov131path)) { // if HGE field extension overlay exists - OWTableOffset = ramAddressOfTable - DSUtils.GetOverlayRAMAddress(131); + OWTableOffset = ramAddressOfTable - OverlayUtils.OverlayTable.GetRAMAddress(131); OWtablePath = ov131path; } else if (ramAddressOfTable >= RomInfo.synthOverlayLoadAddress) { // if the pointer shows the table was moved to the synthetic overlay diff --git a/DS_Map/SpawnEditor.cs b/DS_Map/SpawnEditor.cs index 63a2672..e0a5c6f 100644 --- a/DS_Map/SpawnEditor.cs +++ b/DS_Map/SpawnEditor.cs @@ -52,7 +52,7 @@ namespace DSPRE { Environment.NewLine + "\nProceed?", "Confirmation required", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (d == DialogResult.Yes) { - string moneyOverlayPath = DSUtils.GetOverlayPath(RomInfo.initialMoneyOverlayNumber); + string moneyOverlayPath = OverlayUtils.GetPath(RomInfo.initialMoneyOverlayNumber); ushort headerNumber = ushort.Parse(spawnHeaderComboBox.SelectedItem.ToString().Split()[0]); ARM9.WriteBytes(BitConverter.GetBytes(headerNumber), RomInfo.arm9spawnOffset); @@ -95,13 +95,13 @@ namespace DSPRE { playerDirCombobox.SelectedIndex = BitConverter.ToUInt16(ARM9.ReadBytes(RomInfo.arm9spawnOffset + 16, 2), 0); } private void ReadDefaultMoney() { - if (DSUtils.CheckOverlayHasCompressionFlag(RomInfo.initialMoneyOverlayNumber)) { - if (DSUtils.OverlayIsCompressed(RomInfo.initialMoneyOverlayNumber)) { - DSUtils.DecompressOverlay(RomInfo.initialMoneyOverlayNumber); + if (OverlayUtils.OverlayTable.IsDefaultCompressed(RomInfo.initialMoneyOverlayNumber)) { + if (OverlayUtils.IsCompressed(RomInfo.initialMoneyOverlayNumber)) { + OverlayUtils.Decompress(RomInfo.initialMoneyOverlayNumber); } } - string pathToMoneyOverlay = DSUtils.GetOverlayPath(RomInfo.initialMoneyOverlayNumber); + string pathToMoneyOverlay = OverlayUtils.GetPath(RomInfo.initialMoneyOverlayNumber); initialMoneyUpDown.Value = BitConverter.ToUInt32(DSUtils.ReadFromFile(pathToMoneyOverlay, RomInfo.initialMoneyOverlayOffset, 4), 0); }