Registrarse

[Otros] Como crear tu propia Herramienta para NDS - C#

White

--------------------------------------------------
En este tutorial explicare las funciones básicas para crear una herramienta en Visual Studio C#.
Podréis hacer que vuestra herramienta lea/escriba offsets y descomprima el ARM9 y los narcs.

Simplemente necesitáis tener Visual Studio Community con el componente "Desarrollo de Escritorio de .NET"


Y conocimientos básicos de la sintaxis de C# (Clase/Método/Función, declarar variables, condicionales).


Para comenzar, creamos un proyecto de Windows Forms C#


Se nos abrirá la ventana de diseño donde podemos agregar los botones y cajas de texto, entre otros, para dar forma a nuestra herramienta.
Pero eso ya depende del gusto de cada uno y del aspecto que quiera darle.
Lo importante en este tutorial es el código, y para acceder tan solo hay que pulsar F7.

Una vez aquí, para que el programa reconozca las funciones que vamos a utilizar debemos añadir "using System.IO;" bajo las otras referencias.


Y ya podremos empezar a programar.

Lo esencial para que nuestra herramienta funcione, es obviamente que pueda cargar la ROM con la que vamos a trabajar.

Para ello simplemente utilizaremos la clase "OpenFileDialog".
Empezaremos añadiendo el siguiente código debajo de "Form1"
Código:
OpenFileDialog ofd = new OpenFileDialog();
Y quedara de esta forma:

(Lo común es llamarlo "ofd" para reconocerlo fácilmente, pero podéis poner el nombre que queráis)

Ahora, nos dirigiremos a un método, lo mas recomendable es que sea de un botón y pondremos el siguiente código:

Código:
 ofd.Filter = "NDS File(*.nds)|*.nds";
ofd.ShowDialog();
(Para agregar un botón os vais a "Form1.cs [Diseño]", seleccionáis el botón de la caja de herramientas y pulsáis en la ventana. Luego simplemente al darle doble clic sobre el os llevara al método en el código.)

En este caso el filtro es para archivos .nds, lo que quiere decir que cuando se abra la ventana, solo nos dejara elegir archivos con esta extensión.
Si por ejemplo quisiésemos hacer una herramienta para GBA tendríamos que poner .gba.

Una vez que nuestra herramienta ha cargado la ROM, lo siguiente seria reconocer que versión y que idioma es.

Para ello utilizaremos la clase "BinaryReader".
Lo normal es que este código vaya en el método del botón donde hemos puesto el "OpenFileDialog" y seria el siguiente:
Código:
using (BinaryReader br = new BinaryReader(File.OpenRead(ofd.FileName)))
{
string gameCode = string.Empty;
                br.BaseStream.Seek(0xC, SeekOrigin.Begin);
                gameCode = Encoding.UTF8.GetString(br.ReadBytes(4));

                br.Close();
}
Lo primero es declarar un BinaryReader que he llamado br, el cual va a leer lo que le digamos del ofd que hemos cargado.

Luego declaramos una nueva variable de tipo string donde se guardara el código de la versión.
Ahora le indicamos al BR de donde debe leer el código, en el caso de las ROMs de NDS esta en 0xC, 0xAC si fuese de GBA.

Ahora le asignamos la variable los 4 bytes que lee de ese offset transformados a tipo string.
Y finalmente cerramos el BR.


Ahora, para diferenciar cada código creamos un condicional (Puede ser if o switch) como el siguiente:
Código:
if (gameCode == "ADAS")
                {
                    textBox1.Text = "Pokémon Diamante";

   
                }

                else if (gameCode == "CPUS")
                {
                    textBox1.Text = "Pokémon Platino";
                   
                }
               
                else
                {
                    textBox1.Text = "???";
                   
                    MessageBox.Show("The loaded game isn't supported");
                }
Podéis modificar el condicional añadiendo todas las versiones e idiomas que queráis que soporte vuestra herramienta.
La "textBox1" que aparece, es una caja de texto que os recomiendo poner para que el usuario sepa que su ROM se ha cargado y cual es la que se ha cargado. (También podéis usar una etiqueta)

Y por ultimo, el else manda un mensaje de error si la ROM que hemos cargado no es ninguna de las registradas.

Ahora os voy a mostrar una variante que podéis utilizar para leer valores de la ROM. En este ejemplo, vamos a leer las coordenadas x e y de aparición del jugador en DPPT.

Código:
BinaryReader br = new BinaryReader(File.OpenRead(ofd.FileName));
            for (int x = 0xF6BF0; x <= 0xF6BF0; x++)
            {

                br.BaseStream.Position = x;
                cordX.Text = br.ReadByte().ToString();



            }

           
            for (int y = 0xF6BF4; y <= 0xF6BF4; y++)
            {

                br.BaseStream.Position = y;
                cordY.Text = br.ReadByte().ToString();



            }

            br.Close();
Declaremos un BR como en el caso anterior pero ahora creamos un "for" el cual va a leer los valores entre los Offsets que asignemos.
Lo que hacemos es decirle el primero y el ultimo que tiene que leer.
Código:
for (int x = PrimeroOffset; x <= UltimoOffset; x++)
Y ahora con
Código:
br.BaseStream.Position = x;
lee los valores entre los dos Offsets asignados.
Por ultimo, cambiamos el valor de una caja de texto por lo que ha leído.

Ahora vayamos con el alma de nuestra herramienta, escribir valores en la ROM.
Para ello utilizaremos BinaryWriter, y su funcionamiento es muy similar a lo que acabamos de ver.
Código:
BinaryWriter bw = new BinaryWriter(File.OpenWrite(ofd.FileName));
            for (int x = 0xF6BF0; x <= 0xF6BF0; x++)
            {

                bw.BaseStream.Position = x;
                bw.Write(cordX.Value); 



            }

            for (int y = 0xF6BF4; y <= 0xF6BF4; y++)
            {

                bw.BaseStream.Position = y;
                bw.Write(cordY.Value); 



            }
            bw.Close();
        }
Lo primero, declarar el BinaryWriter y ahora creamos un for como en el caso anterior para indicar donde tiene que escribir los valores.

Y ahora con el siguiente código los escribimos.
Código:
bw.Write(cordY.Value);
La función es bw.Write(), dentro del paréntesis le decimos lo que tiene que escribir.
En este caso lo que hacemos es decirle que coja el valor de un "NumericUpDown". (cordY.Value)

Si estamos creando una herramienta para NDS, es importante que podamos descomprimir los .narc para acceder a eventos, scripts y mapas entre otros.

Hacer esto es algo mas complejo y tedioso que lo que hemos visto hasta ahora, porque cada ROM tiene direcciones diferentes por lo que si queremos que nuestra herramienta sea multiversión tendremos que añadir todas estas rutas.

Lo primero es añadir las siguientes referencias:
Código:
using System.IO;
using System.Resources;
using NarcAPI;
using System.Threading;
using System.Reflection;
using System.Globalization;
using System.Diagnostics;
Para que "NarcAPI" funcione necesitareis descargar este archivo:
https://mega.nz/#!Zp8FmYaR!C6qzJNbYFi4Ny3i-8Hw-q3-vX21u_A2enb8nhqtARus

Y meterlo en la carpeta del proyecto.
Luego en el explorador de soluciones, hacéis clic derecho, agregar, elemento existente y lo seleccionáis.


En este ejemplo mostrare como hacerlo con la ROM de Diamante en Español.

Lo primero es declarar una variable de tipo static string (dentro de la clase y encima del método "Form1")
Código:
public static string workingFolder;
Ahora dentro de la comprobación del código de la ROM escribiremos lo siguiente:
Código:
workingFolder = Path.GetDirectoryName(ofd.FileName) + "\\" + Path.GetFileNameWithoutExtension(ofd.FileName) + "_NSM" + "\\";
Esto creara una nueva carpeta en la carpeta donde se encuentre la ROM que hemos leído.
El nombre de la carpeta sera el de la ROM seguido de un identificador "_NSM" (NDS Script Maker), podéis poner el que queráis, por ejemplo las siglas de vuestra herramienta como yo he hecho.

Y ahora elegimos los .narc que vamos a extraer a esa carpeta:
Código:
Narc.Open(workingFolder + @"data\fielddata\mapmatrix\map_matrix.narc").ExtractToFolder(workingFolder + @"data\fielddata\mapmatrix\map_matrix");
Narc.Open(workingFolder + @"data\fielddata\land_data\land_data_release.narc").ExtractToFolder(workingFolder + @"data\fielddata\land_data\land_data_release");
Narc.Open(workingFolder + @"data\fielddata\build_model\build_model.narc").ExtractToFolder(workingFolder + @"data\fielddata\build_model\build_model");
Narc.Open(workingFolder + @"data\fielddata\areadata\area_map_tex\map_tex_set.narc").ExtractToFolder(workingFolder + @"data\fielddata\areadata\area_map_tex\map_tex_set");
Narc.Open(workingFolder + @"data\fielddata\areadata\area_build_model\areabm_texset.narc").ExtractToFolder(workingFolder + @"data\fielddata\areadata\area_build_model\areabm_texset");
Narc.Open(workingFolder + @"data\msgdata\msg.narc").ExtractToFolder(workingFolder + @"data\msgdata\msg");
Narc.Open(workingFolder + @"data\fielddata\script\scr_seq_release.narc").ExtractToFolder(workingFolder + @"data\fielddata\script\scr_seq_release");
Narc.Open(workingFolder + @"data\fielddata\areadata\area_data.narc").ExtractToFolder(workingFolder + @"data\fielddata\areadata\area_data");
Narc.Open(workingFolder + @"data\fielddata\areadata\area_build_model\area_build.narc").ExtractToFolder(workingFolder + @"data\fielddata\areadata\area_build_model\area_build");
Narc.Open(Form1.workingFolder + @"data\fielddata\eventdata\zone_event_release.narc").ExtractToFolder(Form1.workingFolder + @"data\fielddata\eventdata\zone_event_release");
Esto es lo que deberéis cambiar en el caso de que sea otra ROM con otras direcciones.

Ahora, para descomprimir el ARM9 hacemos lo siguiente:
Código:
System.IO.BinaryReader readArm9 = new System.IO.BinaryReader(File.OpenRead(workingFolder + @"arm9.bin"));
readArm9.BaseStream.Position = 0xEEE08;
La dirección que en este caso es "0xEEE08" depende de la ROM y tendréis que investigar que direcciones corresponden a cada una.
Si no encontráis ningún post donde este documentado podéis abrir programas de código libre como SDSME para buscarlo.


Y ahora finalmente, creáis un método como este:
Código:
private void unpackROM()
        {
            Directory.CreateDirectory(workingFolder);
            Process unpack = new Process();
            unpack.StartInfo.FileName = @"Data\ndstool.exe";
            unpack.StartInfo.Arguments = "-v -x " + '"' + ndsFileName + '"' + " -9 " + '"' + workingFolder + "arm9.bin" + '"' + " -7 " + '"' + workingFolder + "arm7.bin" + '"' + " -y9 " + '"' + workingFolder + "y9.bin" + '"' + " -y7 " + '"' + workingFolder + "y7.bin" + '"' + " -d " + '"' + workingFolder + "data" + '"' + " -y " + '"' + workingFolder + "overlay" + '"' + " -t " + '"' + workingFolder + "banner.bin" + '"' + " -h " + '"' + workingFolder + "header.bin" + '"';
            Application.DoEvents();
            unpack.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
            unpack.StartInfo.CreateNoWindow = true;
            unpack.Start();
            unpack.WaitForExit();
        }
Para que esto funcione necesitareis este programa:
https://mega.nz/#!Ass1HCpD!xD4y6MBzev8gbDVhKzJvx2RvMRPRaLJowRntqioL4-w

Dentro de la carpeta de vuestro proyecto, creáis una llamada "Data" y lo metéis.
Luego lo añadís al explorador de soluciones como con el Narc.cs.

Por ultimo solo tenéis que llamar a este método desde el condicional que lee el código de la ROM para que empiece a extraer todo.

Para volver a comprimir los narcs y guardar la ROM, usaremos el siguiente código:
Código:
SaveFileDialog saveNDS = new SaveFileDialog();
            saveNDS.Filter = "NDS File(*.nds)|*.nds";
            saveNDS.FileName = Path.GetFileNameWithoutExtension(ofd.FileName);
            if (saveNDS.ShowDialog() == DialogResult.OK)
            {
                if (gameID == 0x45414441 || gameID == 0x45415041 || gameID == 0x53414441 || gameID == 0x53415041 || gameID == 0x46414441 || gameID == 0x46415041 || gameID == 0x49414441 || gameID == 0x49415041 || gameID == 0x44414441 || gameID == 0x44415041 || gameID == 0x4B414441 || gameID == 0x4B415041)
                {
                    Narc.FromFolder(workingFolder + @"data\fielddata\mapmatrix\map_matrix\").Save(workingFolder + @"data\fielddata\mapmatrix\map_matrix.narc");
                    Narc.FromFolder(workingFolder + @"data\fielddata\land_data\land_data_release").Save(workingFolder + @"data\fielddata\land_data\land_data_release.narc");
                    Narc.FromFolder(workingFolder + @"data\fielddata\build_model\build_model").Save(workingFolder + @"data\fielddata\build_model\build_model.narc");
                    Narc.FromFolder(workingFolder + @"data\fielddata\areadata\area_map_tex\map_tex_set").Save(workingFolder + @"data\fielddata\areadata\area_map_tex\map_tex_set.narc");
                    Narc.FromFolder(workingFolder + @"data\fielddata\areadata\area_build_model\areabm_texset").Save(workingFolder + @"data\fielddata\areadata\area_build_model\areabm_texset.narc");
                    Narc.FromFolder(workingFolder + @"data\msgdata\msg\").Save(workingFolder + @"data\msgdata\msg.narc");
                    Narc.FromFolder(workingFolder + @"data\fielddata\script\scr_seq_release").Save(workingFolder + @"data\fielddata\script\scr_seq_release.narc");
                    Narc.FromFolder(workingFolder + @"data\fielddata\eventdata\zone_event_release").Save(workingFolder + @"data\fielddata\eventdata\zone_event_release.narc");
                    Narc.FromFolder(workingFolder + @"data\fielddata\areadata\area_data").Save(workingFolder + @"data\fielddata\areadata\area_data.narc");
                    Narc.FromFolder(workingFolder + @"data\fielddata\areadata\area_build_model\area_build").Save(workingFolder + @"data\fielddata\areadata\area_build_model\area_build.narc");
                    Directory.Delete(workingFolder + @"data\fielddata\mapmatrix\map_matrix", true);
                    Directory.Delete(workingFolder + @"data\fielddata\land_data\land_data_release", true);
                    Directory.Delete(workingFolder + @"data\fielddata\build_model\build_model", true);
                    Directory.Delete(workingFolder + @"data\fielddata\areadata\area_map_tex\map_tex_set", true);
                    Directory.Delete(workingFolder + @"data\fielddata\areadata\area_build_model\areabm_texset", true);
                    Directory.Delete(workingFolder + @"data\msgdata\msg", true);
                    Directory.Delete(workingFolder + @"data\fielddata\script\scr_seq_release", true);
                    Directory.Delete(workingFolder + @"data\fielddata\eventdata\zone_event_release", true);
                    Directory.Delete(workingFolder + @"data\fielddata\areadata\area_data", true);
                    Directory.Delete(workingFolder + @"data\fielddata\areadata\area_build_model\area_build", true);
                    Process repack = new Process();
                    repack.StartInfo.FileName = @"Data\ndstool.exe";
                    repack.StartInfo.Arguments = "-c " + '"' + saveNDS.FileName + '"' + " -9 " + '"' + workingFolder + "arm9.bin" + '"' + " -7 " + '"' + workingFolder + "arm7.bin" + '"' + " -y9 " + '"' + workingFolder + "y9.bin" + '"' + " -y7 " + '"' + workingFolder + "y7.bin" + '"' + " -d " + '"' + workingFolder + "data" + '"' + " -y " + '"' + workingFolder + "overlay" + '"' + " -t " + '"' + workingFolder + "banner.bin" + '"' + " -h " + '"' + workingFolder + "header.bin" + '"';
                    Application.DoEvents();
                    repack.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
                    repack.StartInfo.CreateNoWindow = true;
                    repack.Start();
                    repack.WaitForExit();
                    Narc.Open(workingFolder + @"data\fielddata\mapmatrix\map_matrix.narc").ExtractToFolder(workingFolder + @"data\fielddata\mapmatrix\map_matrix");
                    Narc.Open(workingFolder + @"data\fielddata\land_data\land_data_release.narc").ExtractToFolder(workingFolder + @"data\fielddata\land_data\land_data_release");
                    Narc.Open(workingFolder + @"data\fielddata\build_model\build_model.narc").ExtractToFolder(workingFolder + @"data\fielddata\build_model\build_model");
                    Narc.Open(workingFolder + @"data\fielddata\areadata\area_map_tex\map_tex_set.narc").ExtractToFolder(workingFolder + @"data\fielddata\areadata\area_map_tex\map_tex_set");
                    Narc.Open(workingFolder + @"data\fielddata\areadata\area_build_model\areabm_texset.narc").ExtractToFolder(workingFolder + @"data\fielddata\areadata\area_build_model\areabm_texset");
                    Narc.Open(workingFolder + @"data\msgdata\msg.narc").ExtractToFolder(workingFolder + @"data\msgdata\msg\");
                    Narc.Open(workingFolder + @"data\fielddata\script\scr_seq_release.narc").ExtractToFolder(workingFolder + @"data\fielddata\script\scr_seq_release");
                    Narc.Open(workingFolder + @"data\fielddata\eventdata\zone_event_release.narc").ExtractToFolder(workingFolder + @"data\fielddata\eventdata\zone_event_release");
                    Narc.Open(workingFolder + @"data\fielddata\areadata\area_data.narc").ExtractToFolder(workingFolder + @"data\fielddata\areadata\area_data");
                    Narc.Open(workingFolder + @"data\fielddata\areadata\area_build_model\area_build.narc").ExtractToFolder(workingFolder + @"data\fielddata\areadata\area_build_model\area_build");
                }
Este código debería ir en un nuevo botón para guardar la ROM.
Como podéis ver, en la condición en vez de poner el código como texto lo he puesto directamente como Hex, de esta forma aunque para el desarrollador se puede hacer mas tedioso de leer, el código queda mas limpio y optimo.

Faltaría editar los narcs antes de volver comprimirlos para guardar la ROM, pero eso ira en un tutorial aparte porque es algo mas complejo.



-xGal (Tutorial para crear herramientas GBA)
-SDSME (Codigo libre tomado como ejemplo)
-Creditos a Bag (Por no hacer nada)
 
Última edición por un moderador:
Arriba