public static PSO2Text GetTextConditional(string normalPath, string overridePath, string textFileName) { PSO2Text partsText = new PSO2Text(); string partsPath = null; if (File.Exists(overridePath)) { partsPath = overridePath; } else if (File.Exists(normalPath)) { partsPath = normalPath; } else { return(null); } if (partsPath != null) { var strm = new MemoryStream(File.ReadAllBytes(partsPath)); var partsIce = IceFile.LoadIceFile(strm); strm.Dispose(); List <byte[]> files = (new List <byte[]>(partsIce.groupOneFiles)); files.AddRange(partsIce.groupTwoFiles); //Loop through files to get what we need foreach (byte[] file in files) { if (IceFile.getFileName(file).ToLower().Contains(textFileName)) { partsText = ReadPSO2Text(file); } } partsIce = null; } return(partsText); }
public static PSO2Text ReadNIFLText(BufferedStreamReader streamReader, int offset, string fileName) { var txt = new PSO2Text(); var nifl = streamReader.Read <AquaCommon.NIFL>(); var end = nifl.NOF0Offset + offset; var rel0 = streamReader.Read <AquaCommon.REL0>(); streamReader.Seek(rel0.REL0DataStart + offset, SeekOrigin.Begin); var categoryPointer = streamReader.Read <int>(); var categoryCount = streamReader.Read <int>(); //Read through categories streamReader.Seek(categoryPointer + offset, SeekOrigin.Begin); for (int i = 0; i < categoryCount; i++) { var categoryNameOffset = streamReader.Read <int>(); var categoryDataInfoOffset = streamReader.Read <int>(); var subCategoryCount = streamReader.Read <int>(); //Setup subcategory lists txt.text.Add(new List <List <PSO2Text.textPair> >()); for (int j = 0; j < subCategoryCount; j++) { txt.text[i].Add(new List <PSO2Text.textPair>()); } //Get category name long bookmark = streamReader.Position(); streamReader.Seek(categoryNameOffset + offset, SeekOrigin.Begin); txt.categoryNames.Add(ReadCString(streamReader)); //Get Category Info streamReader.Seek(categoryDataInfoOffset + offset, SeekOrigin.Begin); for (int sub = 0; sub < subCategoryCount; sub++) { var categoryIndexOffset = streamReader.Read <int>(); var subCategoryId = streamReader.Read <int>(); var categoryIndexCount = streamReader.Read <int>(); var bookMarkSub = streamReader.Position(); streamReader.Seek(categoryIndexOffset + offset, SeekOrigin.Begin); for (int j = 0; j < categoryIndexCount; j++) { var pair = new PSO2Text.textPair(); int nameLoc = streamReader.Read <int>(); int textLoc = streamReader.Read <int>(); long bookmarkLocal = streamReader.Position(); streamReader.Seek(nameLoc + offset, SeekOrigin.Begin); pair.name = ReadCString(streamReader); streamReader.Seek(textLoc + offset, SeekOrigin.Begin); pair.str = ReadUTF16String(streamReader, end); txt.text[i][subCategoryId].Add(pair); streamReader.Seek(bookmarkLocal, SeekOrigin.Begin); } streamReader.Seek(bookMarkSub, SeekOrigin.Begin); } streamReader.Seek(bookmark, SeekOrigin.Begin); } #if DEBUG if (fileName != null) { StringBuilder output = new StringBuilder(); for (int i = 0; i < txt.text.Count; i++) { output.AppendLine(txt.categoryNames[i]); for (int j = 0; j < txt.text[i].Count; j++) { output.AppendLine($"Group {j}"); for (int k = 0; k < txt.text[i][j].Count; k++) { var pair = txt.text[i][j][k]; output.AppendLine($"{pair.name} - {pair.str}"); } output.AppendLine(); } output.AppendLine(); } File.WriteAllText(fileName + ".txt", output.ToString()); } #endif return(txt); }
//Expects somewhat strict formatting, but reads this from a .text file public static PSO2Text ReadPSO2TextFromTxt(string filename) { var lines = File.ReadAllLines(filename); PSO2Text text = new PSO2Text(); //We start on line 3 to avoid the metadata text if (lines.Length < 4) { return(null); } int mode = 0; for (int i = 3; i < lines.Length; i++) { switch (mode) { case 0: //Category if (lines[i] == "") { return(text); } text.categoryNames.Add(lines[i]); text.text.Add(new List <List <PSO2Text.textPair> >()); mode = 1; break; case 1: //Group if (lines[i] == "") { mode = 0; } else if (lines[i].Contains("Group")) { text.text[text.text.Count - 1].Add(new List <PSO2Text.textPair>()); mode = 2; } break; case 2: //Text if (lines[i] == "") { mode = 1; } else if (lines[i].Contains('-')) { PSO2Text.textPair pair = new PSO2Text.textPair(); string[] line = lines[i].Split('-'); //Handle - being used as the name for whatever reason. Should be an uncommon issue if (line[0] == "") { pair.name = "-"; lines[i] = ReplaceFirst(lines[i], "- ", ""); } else if (line[0][line[0].Length - 1] == ' ') { pair.name = line[0].Substring(0, line[0].Length - 1); //Get rid of the space } string schrodingersSpace = ""; if (line[1][0].ToString() == " ") { schrodingersSpace = " "; } pair.str = ReplaceFirst(lines[i], line[0] + "-" + schrodingersSpace, ""); //Safely get the next part, in theory. text.text[text.text.Count - 1][text.text[text.text.Count - 1].Count - 1].Add(pair); } break; } } return(text); }
public static void WritePSO2TextNIFL(string outname, PSO2Text pso2Text) { if (pso2Text == null) { return; } List <byte> finalOutBytes = new List <byte>(); int rel0SizeOffset = 0; int categoryOffset = 0; List <byte> outBytes = new List <byte>(); List <PSO2Text.textPairLocation> textPairs = new List <PSO2Text.textPairLocation>(); List <PSO2Text.textLocation> texts = new List <PSO2Text.textLocation>(); List <Dictionary <string, int> > namePointers = new List <Dictionary <string, int> >(); List <List <int> > textAddresses = new List <List <int> >(); List <int> subCategoryAddress = new List <int>(); List <List <int> > subCategoryNullAddresses = new List <List <int> >(); List <int> nof0PointerLocations = new List <int>(); //Used for the NOF0 section //REL0 outBytes.AddRange(Encoding.UTF8.GetBytes("REL0")); rel0SizeOffset = outBytes.Count; //We'll fill this later outBytes.AddRange(BitConverter.GetBytes(0)); outBytes.AddRange(BitConverter.GetBytes(0)); outBytes.AddRange(BitConverter.GetBytes(0)); outBytes.AddRange(BitConverter.GetBytes(-1)); //Write placeholders for the text pointers //Iterate through each category for (int cat = 0; cat < pso2Text.text.Count; cat++) { namePointers.Add(new Dictionary <string, int>()); textAddresses.Add(new List <int>()); for (int sub = 0; sub < pso2Text.text[cat].Count; sub++) { //Add this for when we write it later textAddresses[cat].Add(outBytes.Count); for (int pair = 0; pair < pso2Text.text[cat][sub].Count; pair++) { NOF0Append(nof0PointerLocations, outBytes.Count, 1); NOF0Append(nof0PointerLocations, outBytes.Count + 4, 1); var pairLoc = new PSO2Text.textPairLocation(); pairLoc.address = outBytes.Count; pairLoc.text = pso2Text.text[cat][sub][pair]; pairLoc.category = cat; textPairs.Add(pairLoc); //Add in placeholder values to fill later outBytes.AddRange(new byte[8]); } } } //Write the subcategory data for (int cat = 0; cat < pso2Text.text.Count; cat++) { subCategoryAddress.Add(outBytes.Count); for (int sub = 0; sub < pso2Text.text[cat].Count; sub++) { NOF0Append(nof0PointerLocations, outBytes.Count, 1); if (subCategoryNullAddresses.Count - 1 < sub) { subCategoryNullAddresses.Add(new List <int>()); } if (pso2Text.text[cat][sub].Count > 0) { outBytes.AddRange(BitConverter.GetBytes(textAddresses[cat][sub])); } else { subCategoryNullAddresses[sub].Add(outBytes.Count); outBytes.AddRange(BitConverter.GetBytes(0)); } outBytes.AddRange(BitConverter.GetBytes(sub)); outBytes.AddRange(BitConverter.GetBytes(pso2Text.text[cat][sub].Count)); } } //Write the category data categoryOffset = outBytes.Count; for (int cat = 0; cat < pso2Text.text.Count; cat++) { var categoryName = new PSO2Text.textLocation(); categoryName.address = outBytes.Count; categoryName.str = pso2Text.categoryNames[cat]; texts.Add(categoryName); NOF0Append(nof0PointerLocations, outBytes.Count, 1); NOF0Append(nof0PointerLocations, outBytes.Count + 4, 1); outBytes.AddRange(new byte[4]); outBytes.AddRange(BitConverter.GetBytes(subCategoryAddress[cat])); outBytes.AddRange(BitConverter.GetBytes(pso2Text.text[cat].Count)); } //Write header data SetByteListInt(outBytes, rel0SizeOffset + 4, outBytes.Count); NOF0Append(nof0PointerLocations, outBytes.Count, 1); outBytes.AddRange(BitConverter.GetBytes(categoryOffset)); outBytes.AddRange(BitConverter.GetBytes(pso2Text.text.Count)); //Write main text as null terminated strings for (int i = 0; i < textPairs.Count; i++) { //Write the internal name if (namePointers[textPairs[i].category].ContainsKey(textPairs[i].text.name)) { SetByteListInt(outBytes, textPairs[i].address, namePointers[textPairs[i].category][textPairs[i].text.name]); } else { SetByteListInt(outBytes, textPairs[i].address, outBytes.Count); namePointers[textPairs[i].category].Add(textPairs[i].text.name, outBytes.Count); outBytes.AddRange(Encoding.UTF8.GetBytes(textPairs[i].text.name)); outBytes.Add(0); AlignWriter(outBytes, 0x4); } //Write the text SetByteListInt(outBytes, textPairs[i].address + 4, outBytes.Count); outBytes.AddRange(Encoding.Unicode.GetBytes(textPairs[i].text.str)); outBytes.AddRange(BitConverter.GetBytes((ushort)0)); AlignWriter(outBytes, 0x4); } //Write category text for (int i = 0; i < texts.Count; i++) { //Write the internal name SetByteListInt(outBytes, texts[i].address, outBytes.Count); outBytes.AddRange(Encoding.UTF8.GetBytes(texts[i].str)); outBytes.Add(0); AlignWriter(outBytes, 0x4); } //Unknown data? Don't write if it's not needed. May just be debug related. Empty groups/subcategories point here, but it's possible you could direct them to a general 0. //NA .text files seem to write this slightly differently, writing only one of these while JP .text files write one for every category, seemingly. //For the sake of sanity and space, NA's style will be used for custom .text for (int i = 0; i < pso2Text.text[0].Count; i++) { if (pso2Text.text[0][0].Count > 0) { outBytes.AddRange(BitConverter.GetBytes(i)); } else { for (int groupCounter = 0; groupCounter < subCategoryNullAddresses[i].Count; groupCounter++) { SetByteListInt(outBytes, subCategoryNullAddresses[i][groupCounter], outBytes.Count); } outBytes.AddRange(BitConverter.GetBytes(0)); } } AlignWriter(outBytes, 0x10); //Write REL0 Size SetByteListInt(outBytes, rel0SizeOffset, outBytes.Count - 0x8); //Write NOF0 int NOF0Offset = outBytes.Count; int NOF0Size = (nof0PointerLocations.Count + 2) * 4; int NOF0FullSize = NOF0Size + 0x8; outBytes.AddRange(Encoding.UTF8.GetBytes("NOF0")); outBytes.AddRange(BitConverter.GetBytes(NOF0Size)); outBytes.AddRange(BitConverter.GetBytes(nof0PointerLocations.Count)); outBytes.AddRange(BitConverter.GetBytes(0x10)); //Write pointer offsets for (int i = 0; i < nof0PointerLocations.Count; i++) { outBytes.AddRange(BitConverter.GetBytes(nof0PointerLocations[i])); } NOF0FullSize += AlignWriter(outBytes, 0x10); //NEND outBytes.AddRange(Encoding.UTF8.GetBytes("NEND")); outBytes.AddRange(BitConverter.GetBytes(0x8)); outBytes.AddRange(BitConverter.GetBytes(0)); outBytes.AddRange(BitConverter.GetBytes(0)); //Generate NIFL AquaCommon.NIFL nifl = new AquaCommon.NIFL(); nifl.magic = BitConverter.ToInt32(Encoding.UTF8.GetBytes("NIFL"), 0); nifl.NIFLLength = 0x18; nifl.unkInt0 = 1; nifl.offsetAddition = 0x20; nifl.NOF0Offset = NOF0Offset; nifl.NOF0OffsetFull = NOF0Offset + 0x20; nifl.NOF0BlockSize = NOF0FullSize; nifl.padding0 = 0; //Write NIFL outBytes.InsertRange(0, ConvertStruct(nifl)); File.WriteAllBytes(outname, outBytes.ToArray()); }
public static void WritePSO2Text(string outname, PSO2Text pso2Text) { WritePSO2TextNIFL(outname, pso2Text); }