using System; using System.Collections; using System.Collections.Generic; using System.IO; namespace OpenVIII { /// /// Loads strings from FF8 files /// public partial class Strings { #region Classes public abstract class StringsBase : IReadOnlyDictionary> { #region Fields protected Memory.Archive Archive; protected string[] FileNames; protected StringFile StringFiles; protected FF8StringReference.Settings Settings; #endregion Fields #region Constructors protected void SetValues(Memory.Archive archive, params string[] fileNames) { Memory.Log.WriteLine($"{nameof(StringsBase)}::{nameof(SetValues)} Archive= \"{archive}\", Files= {{{string.Join(", ", fileNames)}}}"); Archive = archive; FileNames = fileNames; } #endregion Constructors #region Indexers public FF8StringReference this[int sectionID, int stringID] => StringFiles?[sectionID, stringID]; #endregion Indexers #region Methods public Memory.Archive GetArchive() => Archive; public IReadOnlyList GetFileNames() => FileNames; public StringFile GetFiles() => StringFiles; /// /// /// So you read the pointers at location, you get so many pointers then skip so many /// bytes before getting more pointers. Do this till start of next section. /// /// /// BinaryReader where data is. /// file you are reading from /// Section where pointers are. /// Section where strings are /// Get so many pointers /// Then skip so many bytes protected void Get_Strings_BinMSG(BinaryReader br, string filename, int pointerStart, uint stringStart, uint grab = 0, uint skip = 0) { var fPos = StringFiles.SubPositions[pointerStart]; if (fPos.Seek > br.BaseStream.Length) return; br.BaseStream.Seek(fPos.Seek, SeekOrigin.Begin); if (StringFiles.SPositions.ContainsKey(pointerStart)) { } else { ushort b = 0; var last = b; var tmp = new List(); uint g = 1; while (br.BaseStream.Position < fPos.Max) { b = br.ReadUInt16(); if (last > b) break; else { if (b != 0xFFFF) { tmp.Add(new FF8StringReference(Archive, filename, b + stringStart, settings: Settings)); last = b; } else tmp.Add(null); if (grab <= 0 || ++g <= grab) continue; br.BaseStream.Seek(skip, SeekOrigin.Current); g = 1; } } StringFiles.SPositions.Add(pointerStart, tmp); } } protected void Get_Strings_ComplexStr(BinaryReader br, string filename, int key, IReadOnlyList list) { var fPaddings = MenuGroupReadPadding(br, StringFiles.SubPositions[key], 1); StringFiles.SPositions.Add(key, new List()); if (fPaddings == null) return; for (uint p = 0; p < fPaddings.Length; p += 2) { key = list[(int)fPaddings[(int)p + 1]]; var fPos = StringFiles.SubPositions[(int)key]; var fPad = fPaddings[p] + fPos.Seek; br.BaseStream.Seek(fPad, SeekOrigin.Begin); if (!StringFiles.SPositions.ContainsKey(key)) StringFiles.SPositions.Add(key, new List()); br.BaseStream.Seek(fPad + 6, SeekOrigin.Begin); //byte[] UNK = br.ReadBytes(6); var len = br.ReadUInt16(); var stop = (uint)(br.BaseStream.Position + len - 9); //6 for UNK, 2 for len 1, for end null StringFiles.SPositions[key].Add(new FF8StringReference(Archive, filename, (uint)br.BaseStream.Position, settings: Settings)); //entry contains possible more than one string so I am scanning for null while (br.BaseStream.Position + 1 < stop) { var b = br.ReadByte(); if (b == 0) StringFiles.SPositions[key].Add(new FF8StringReference(Archive, filename, (uint)br.BaseStream.Position, settings: Settings)); } } } /// /// TODO: make this work with more than one file. /// /// /// /// /// protected void Get_Strings_Offsets(BinaryReader br, string filename, int key, bool pad = false) { var fPos = StringFiles.SubPositions[key]; var fPaddings = pad ? MenuGroupReadPadding(br, fPos) : (new uint[] { 1 }); if (fPaddings == null) return; StringFiles.SPositions.Add(key, new List()); for (uint p = 0; p < fPaddings.Length; p++) { if (fPaddings[p] <= 0) continue; var fPad = pad ? fPaddings[p] + fPos.Seek : fPos.Seek; if (fPad > br.BaseStream.Length) return; br.BaseStream.Seek(fPad, SeekOrigin.Begin); if (br.BaseStream.Position + 4 >= br.BaseStream.Length) continue; int count = br.ReadUInt16(); for (var i = 0; i < count && br.BaseStream.Position + 2 < br.BaseStream.Length; i++) { uint c = br.ReadUInt16(); if (c >= br.BaseStream.Length || c == 0) continue; c += fPad; //long loc =br.BaseStream.Position; //try //{ // br.BaseStream.Seek(c, SeekOrigin.Begin); // if(br.ReadByte()!=0) StringFiles.SPositions[key].Add(new FF8StringReference(Archive, filename, c, settings: Settings)); // } // finally //{ // br.BaseStream.Seek(loc,SeekOrigin.Begin); //} } } } protected abstract void LoadArchiveFiles(); protected uint[] MenuGroupReadPadding(BinaryReader br, Loc fPos, int type = 0) { if (fPos.Seek > br.BaseStream.Length) return null; br.BaseStream.Seek(fPos.Seek, SeekOrigin.Begin); var size = type == 0 ? br.ReadUInt16() : br.ReadUInt32(); var fPaddings = new uint[type == 0 ? size : size * type * 2]; for (var i = 0; i < fPaddings.Length; i += 1 + type) { fPaddings[i] = br.ReadUInt16(); if (type == 0 && fPaddings[i] + fPos.Seek >= fPos.Max) fPaddings[i] = 0; for (var j = 1; j < type + 1; j++) { fPaddings[i + j] = br.ReadUInt16(); } } return fPaddings; } protected abstract void DefaultValues(); public static T Load() where T : StringsBase, new() { Memory.Log.WriteLine($"{nameof(StringsBase)} :: {nameof(Load)} :: {typeof(T)}"); var r = new T(); r.DefaultValues(); r.LoadArchiveFiles(); return r; } protected void LoadArchiveFiles_Simple() { var aw = ArchiveWorker.Load(Archive, true); var buffer = aw.GetBinaryFile(FileNames[0],true); if (buffer == null) return; using (var br = new BinaryReader(new MemoryStream(buffer, true))) { StringFiles = new StringFile(1); StringFiles.SubPositions.Add(Loc.CreateInstance(0, uint.MaxValue)); Get_Strings_Offsets(br, FileNames[0], 0); } } public IEnumerator>> GetEnumerator() { return StringFiles.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable) StringFiles).GetEnumerator(); } public int Count => StringFiles.Count; public bool ContainsKey(int key) { return StringFiles.ContainsKey(key); } public bool TryGetValue(int key, out List value) { return StringFiles.TryGetValue(key, out value); } public List this[int key] => ((IReadOnlyDictionary>) StringFiles)[key]; public IEnumerable Keys => StringFiles.Keys; public IEnumerable> Values => StringFiles.Values; } #endregion Methods } #endregion Classes }