public IEnumerable <FileModResult> TryApply(FileStorage storage)
        {
            var voicetable = storage.TryGetDuplicate(new HyoutaUtils.Checksum.SHA1(0x90a5c55ed954d771ul, 0x11563b9f3bb62ce7ul, 0xc534135au));

            if (voicetable == null)
            {
                return(null);
            }

            // remove the new-for-PC english voice clips from the JP voice table
            var data = new VoiceTable(voicetable, EndianUtils.Endianness.LittleEndian);

            data.Entries.RemoveRange(9495, data.Entries.Count - 9495);
            MemoryStream newvoicetable = new MemoryStream();

            data.WriteToStream(newvoicetable, EndianUtils.Endianness.LittleEndian);

            // the PS3 and PS4 versions disagree slightly on some lines, I took the PS3 timings here but maybe the PS4 ones are better?
            var newvoicetiming3 = storage.TryGetDuplicate(new HyoutaUtils.Checksum.SHA1(0x60e82f9eb05d4b5dul, 0x15fbff5e3b341b0cul, 0xabfcae9du));

            //var newvoicetiming4 = storage.TryGetDuplicate(new HyoutaUtils.Checksum.SHA1(0xf94e4188145f004cul, 0x86ae6fa0b6ff8a1bul, 0x818b4562u));
            if (newvoicetiming3 == null)
            {
                return(null);
            }

            return(new FileModResult[] {
                new FileModResult("data/text/dat/t_voice.tbl", newvoicetable),
                new FileModResult("data/text/dat/t_vctiming.tbl", newvoicetiming3),
            });
        }
 public void UpdateTable()
 {
     voicesListView.SetObjects(table = SongPlayer.Instance.Song.VoiceTable);
     subVoicesListView.ClearObjects();
     Text = $"GBA Music Studio ― VoiceTable Editor (0x{table.GetOffset():X7})";
     voicesListView.SelectedIndex = 0;
 }
Esempio n. 3
0
        public IEnumerable <FileModResult> TryApply(FileStorage storage)
        {
            var voicetable = storage.TryGetDuplicate(new HyoutaUtils.Checksum.SHA1(0xdc8fa92820abc1b4ul, 0x6a646b4d75ba5d23ul, 0x9bd22ee9u));
            var saraclip   = storage.TryGetDuplicate(new HyoutaUtils.Checksum.SHA1(0x0037f27d49910859ul, 0x38a613aae6000493ul, 0x68768874u));
            var alisaclip  = storage.TryGetDuplicate(new HyoutaUtils.Checksum.SHA1(0xe9bd9a1d50cf6170ul, 0x728942e9fead6f2bul, 0xa635fcc6u));

            if (voicetable == null || saraclip == null || alisaclip == null)
            {
                return(null);
            }

            var data = new VoiceTable(voicetable);

            data.Entries.Find(x => x.Index == 64300).Name  = "pc8v10299";
            data.Entries.Find(x => x.Index == 61752).Name += "_a";             // alternate take of the same line; PS4 uses this one and I agree, it's the better take in that context

            MemoryStream newvoicetable = new MemoryStream();

            data.WriteToStream(newvoicetable, EndianUtils.Endianness.LittleEndian);

            // We use the PS4 timings for here because several lines (particularly in the Prologue) were re-recorded between PS3 and PC
            // and the PS4 timings should match that better.
            // Note that the PC version by default uses timings that exactly match the PS3 version, so they were not updated for that release.
            // NOTE: I originally thought I needed to copy e8v04585 from 3 to 4 since we use the PS3 line there
            // (Emma's "class spirit" line in the final dungeon) but it turns out those have the same timings in both versions,
            // so that's not actually necessary.
            //var voicetiming3 = storage.TryGetDuplicate(new HyoutaUtils.Checksum.SHA1(0x4530f8f99feffdd6ul, 0xbdb3b6e43ab769d0ul, 0xa0e9d5d8u));
            var voicetiming4 = storage.TryGetDuplicate(new HyoutaUtils.Checksum.SHA1(0x47b8e01924115c99ul, 0x14f4a5fa65873309ul, 0xc5825a41u));

            if (voicetiming4 == null)
            {
                return(null);
            }

            return(new FileModResult[] {
                new FileModResult("data/text/dat_us/t_voice.tbl", newvoicetable),
                new FileModResult("data/text/dat_us/t_vctiming.tbl", voicetiming4),
                new FileModResult("data/voice/wav/pc8v10299.wav", saraclip),
                new FileModResult("data/voice/wav/pc8v02551.wav", alisaclip),
            });
        }
        public static void GenerateEnFiles(bool prefer_ps3_over_ps4)
        {
            // we pretty much do the same as JP here but we have to do an ID remapping, because the PS4 internal IDs for the PC audio files are different
            VoiceTable canonicalEnVoiceTable;

            using (var fs = new HyoutaUtils.Streams.DuplicatableFileStream(@"c:\_tmp_a\_cs1-voicetiming\__pc-senpatcher\t_voice_US.tbl")) {
                canonicalEnVoiceTable = new VoiceTable(fs, EndianUtils.Endianness.LittleEndian);
            }
            VoiceTable ps4EnVoiceTable;

            using (var fs = new HyoutaUtils.Streams.DuplicatableFileStream(@"c:\_tmp_a\_cs1-voicetiming\_ps4-us\t_voice.tbl")) {
                ps4EnVoiceTable = new VoiceTable(fs, EndianUtils.Endianness.LittleEndian);
            }

            Dictionary <string, List <VoiceTableEntry> > pcFilenameToInternalIdMap = new Dictionary <string, List <VoiceTableEntry> >();

            foreach (var a in canonicalEnVoiceTable.Entries)
            {
                if (!pcFilenameToInternalIdMap.ContainsKey(a.Name))
                {
                    pcFilenameToInternalIdMap.Add(a.Name, new List <VoiceTableEntry>());
                }
                pcFilenameToInternalIdMap[a.Name].Add(a);
            }
            Dictionary <string, List <VoiceTableEntry> > ps4FilenameToInternalIdMap = new Dictionary <string, List <VoiceTableEntry> >();

            foreach (var a in ps4EnVoiceTable.Entries)
            {
                string n = a.Name == "pc8v10286" ? "pc8v10299" : a.Name == "pc8v10286_6" ? "pc8v10286" : a.Name;                 // need to match my remapping of the overwritten sara line
                if (!ps4FilenameToInternalIdMap.ContainsKey(n))
                {
                    ps4FilenameToInternalIdMap.Add(n, new List <VoiceTableEntry>());
                }
                ps4FilenameToInternalIdMap[n].Add(a);
            }

            if (pcFilenameToInternalIdMap.Count != ps4FilenameToInternalIdMap.Count)
            {
                // shouldn't happen with correct input
                Console.WriteLine($"global count mismatch PC {pcFilenameToInternalIdMap.Count} <=> PS4 {ps4FilenameToInternalIdMap.Count}");
            }

            foreach (var kvp in pcFilenameToInternalIdMap)
            {
                var pc  = kvp.Value;
                var ps4 = ps4FilenameToInternalIdMap[kvp.Key];
                if (pc.Count != ps4.Count)
                {
                    if (pc.Count == 2 && ps4.Count == 1)
                    {
                        // just dupe the PS4 entry to get it to match 1:1
                        ps4.Add(new VoiceTableEntry(ps4[0]));
                    }
                    else
                    {
                        // shouldn't happen with correct input
                        Console.WriteLine($"count mismatch PC {pc.Count} <=> PS4 {ps4.Count}");
                    }
                }
            }

            // okay now we have a 1:1 mapping between PC and PS4 IDs; we can apply this on the PS4 voice timing file to get a PC-compatible one

            VoiceTiming enPs3PcVoiceTiming;             // is the same file in both versions, just endian-flipped

            using (var fs = new HyoutaUtils.Streams.DuplicatableFileStream(@"c:\_tmp_a\_cs1-voicetiming\_pc-us\t_vctiming.tbl")) {
                enPs3PcVoiceTiming = new VoiceTiming(fs, EndianUtils.Endianness.LittleEndian);
            }

            VoiceTiming enPs4USVoiceTiming;

            using (var fs = new HyoutaUtils.Streams.DuplicatableFileStream(@"c:\_tmp_a\_cs1-voicetiming\_ps4-us\t_vctiming_us.tbl")) {
                enPs4USVoiceTiming = new VoiceTiming(fs, EndianUtils.Endianness.LittleEndian);
            }

            SortedDictionary <ushort, VoiceTimingEntry> voiceTimingDict = new SortedDictionary <ushort, VoiceTimingEntry>();

            foreach (VoiceTimingEntry vte in enPs3PcVoiceTiming.Entries)
            {
                if (voiceTimingDict.ContainsKey(vte.Index))
                {
                    Console.WriteLine($"index {vte.Index} mapped more than once?");
                    var existingMapping = voiceTimingDict[vte.Index];
                    if (existingMapping.TimingData != vte.TimingData)
                    {
                        Console.WriteLine($"timing data mismatch: {existingMapping.TimingData.ToString("x16")} <=> {vte.TimingData.ToString("x16")}");
                    }
                    voiceTimingDict.Remove(vte.Index);
                }
                voiceTimingDict.Add(vte.Index, vte);
            }

            HashSet <ushort> mappedIndices = new HashSet <ushort>();

            foreach (VoiceTableEntry vte in canonicalEnVoiceTable.Entries)
            {
                if (!mappedIndices.Contains(vte.Index))
                {
                    mappedIndices.Add(vte.Index);
                }
            }

            foreach (VoiceTimingEntry vte in enPs4USVoiceTiming.Entries)
            {
                List <VoiceTableEntry> specialPcEntries  = null;
                List <VoiceTableEntry> specialPs4Entries = null;
                ushort remappedIndex = RemapPs4ToPc(vte.Index, pcFilenameToInternalIdMap, ps4FilenameToInternalIdMap, ref specialPcEntries, ref specialPs4Entries);

                ushort[] indices;
                if (specialPcEntries != null)
                {
                    indices = new ushort[2] {
                        specialPcEntries[0].Index, specialPcEntries[1].Index
                    };
                }
                else
                {
                    indices = new ushort[1] {
                        remappedIndex
                    };
                }

                foreach (ushort index in indices)
                {
                    if (mappedIndices.Contains(index))
                    {
                        if (prefer_ps3_over_ps4)
                        {
                            // only insert if there's not already an entry
                            if (!voiceTimingDict.ContainsKey(index))
                            {
                                VoiceTimingEntry vte2 = new VoiceTimingEntry(vte);
                                vte2.Index = index;
                                voiceTimingDict.Add(index, vte2);
                            }
                        }
                        else
                        {
                            // overwrite existing entries
                            if (voiceTimingDict.ContainsKey(index))
                            {
                                voiceTimingDict.Remove(index);
                            }
                            VoiceTimingEntry vte2 = new VoiceTimingEntry(vte);
                            vte2.Index = index;
                            voiceTimingDict.Add(index, vte2);
                        }
                    }
                }
            }

            enPs3PcVoiceTiming.Entries.Clear();
            foreach (var kvp in voiceTimingDict)
            {
                enPs3PcVoiceTiming.Entries.Add(kvp.Value);
            }
            using (var fs = new FileStream(@"c:\_tmp_a\_cs1-voicetiming\__pc-senpatcher\__gen_vctiming_us_ps" + (prefer_ps3_over_ps4 ? "3" : "4") + "_variant.tbl", FileMode.Create)) {
                enPs3PcVoiceTiming.WriteToStream(fs, EndianUtils.Endianness.LittleEndian);
            }
        }
        public static void GenerateJpFiles(bool prefer_ps3_over_ps4)
        {
            VoiceTable canonicalJpVoiceTable;

            using (var fs = new HyoutaUtils.Streams.DuplicatableFileStream(@"c:\_tmp_a\_cs1-voicetiming\__pc-senpatcher\t_voice_JP.tbl")) {
                canonicalJpVoiceTable = new VoiceTable(fs, EndianUtils.Endianness.LittleEndian);
            }

            VoiceTiming jpPs3VoiceTiming;

            using (var fs = new HyoutaUtils.Streams.DuplicatableFileStream(@"c:\_tmp_a\_cs1-voicetiming\_ps3-jp\t_vctiming.tbl")) {
                jpPs3VoiceTiming = new VoiceTiming(fs, EndianUtils.Endianness.BigEndian);
            }

            VoiceTiming jpPs4USVoiceTiming;

            using (var fs = new HyoutaUtils.Streams.DuplicatableFileStream(@"c:\_tmp_a\_cs1-voicetiming\_ps4-us\t_vctiming.tbl")) {
                jpPs4USVoiceTiming = new VoiceTiming(fs, EndianUtils.Endianness.LittleEndian);
            }

            SortedDictionary <ushort, VoiceTimingEntry> voiceTimingDict = new SortedDictionary <ushort, VoiceTimingEntry>();

            foreach (VoiceTimingEntry vte in jpPs3VoiceTiming.Entries)
            {
                if (voiceTimingDict.ContainsKey(vte.Index))
                {
                    Console.WriteLine($"index {vte.Index} mapped more than once?");
                    voiceTimingDict.Remove(vte.Index);
                }
                voiceTimingDict.Add(vte.Index, vte);
            }

            HashSet <ushort> mappedIndices = new HashSet <ushort>();

            foreach (VoiceTableEntry vte in canonicalJpVoiceTable.Entries)
            {
                if (!mappedIndices.Contains(vte.Index))
                {
                    mappedIndices.Add(vte.Index);
                }
            }

            foreach (VoiceTimingEntry vte in jpPs4USVoiceTiming.Entries)
            {
                if (mappedIndices.Contains(vte.Index))
                {
                    if (prefer_ps3_over_ps4)
                    {
                        // only insert if there's not already an entry
                        if (!voiceTimingDict.ContainsKey(vte.Index))
                        {
                            voiceTimingDict.Add(vte.Index, vte);
                        }
                    }
                    else
                    {
                        // overwrite existing entries
                        if (voiceTimingDict.ContainsKey(vte.Index))
                        {
                            voiceTimingDict.Remove(vte.Index);
                        }
                        voiceTimingDict.Add(vte.Index, vte);
                    }
                }
            }


            jpPs3VoiceTiming.Entries.Clear();
            foreach (var kvp in voiceTimingDict)
            {
                jpPs3VoiceTiming.Entries.Add(kvp.Value);
            }
            using (var fs = new FileStream(@"c:\_tmp_a\_cs1-voicetiming\__pc-senpatcher\__gen_vctiming_jp_ps" + (prefer_ps3_over_ps4 ? "3" : "4") + "_variant.tbl", FileMode.Create)) {
                jpPs3VoiceTiming.WriteToStream(fs, EndianUtils.Endianness.LittleEndian);
            }
        }