public static int Execute(List <string> args) { if (args.Count < 2) { Console.WriteLine("Usage: V0000.SIF V0000.VP"); return(-1); } char[] comma = new char[] { ',' }; string[] siflines = System.IO.File.ReadAllLines(args[0]); long filecount = long.Parse(siflines[0].Split(comma)[0]); var offsets = new List <(long offset, long size, string name)>(); for (long i = 1; i < siflines.LongLength; ++i) { string[] split = siflines[i].Split(comma, 3); if (split.Length >= 2) { string name = null; if (split.Length == 3) { name = TryParseFilename(split[2]); } offsets.Add((long.Parse(split[0]), long.Parse(split[1]), name)); } } // make sure we have no duplicate filenames HashSet <string> filenames = new HashSet <string>(); for (int i = 0; i < offsets.Count; ++i) { string filename = offsets[i].name ?? i.ToString("D4"); string basename = filename; int idx = 2; while (filenames.Contains(filename)) { filename = basename + "_" + idx; ++idx; } offsets[i] = (offsets[i].offset, offsets[i].size, filename); filenames.Add(filename); } string outbasename = System.IO.Path.GetFileNameWithoutExtension(args[1]); long sectorsize = 2048; using (var vp = new HyoutaUtils.Streams.DuplicatableFileStream(args[1])) { for (int i = 0; i < offsets.Count; ++i) { using (var fs = new System.IO.FileStream(outbasename + "_" + offsets[i].name, System.IO.FileMode.Create)) { vp.Position = offsets[i].offset * sectorsize; HyoutaUtils.StreamUtils.CopyStream(vp, fs, offsets[i].size); } } } return(0); }
public static int Compress(List <string> args) { string inpath = null; string outpath = null; try { for (int i = 0; i < args.Count; ++i) { switch (args[i]) { default: if (inpath == null) { inpath = args[i]; } else if (outpath == null) { outpath = args[i]; } else { PrintCompressUsage(); return(-1); } break; } } } catch (IndexOutOfRangeException) { PrintCompressUsage(); return(-1); } if (inpath == null) { PrintCompressUsage(); return(-1); } if (outpath == null) { outpath = inpath + ".cmp"; } using (var infile = new HyoutaUtils.Streams.DuplicatableFileStream(inpath)) using (var outfile = new FileStream(outpath, FileMode.Create)) { utf_tab_sharp.CpkCompress.compress(infile, 0, infile.Length, outfile); } return(0); }
private static HyoutaUtils.HyoutaArchive.HyoutaArchiveContainer TryLoadBackupArchive(string backupArchivePath, ProgressReporter progress) { try { if (File.Exists(backupArchivePath)) { progress.Message(string.Format("Loading backup archive at {0}...", backupArchivePath)); using (var fs = new HyoutaUtils.Streams.DuplicatableFileStream(backupArchivePath)) { return(new HyoutaUtils.HyoutaArchive.HyoutaArchiveContainer(fs)); } } } catch (Exception ex) { progress.Error(string.Format("Failed to load backup file archive: {0}", ex.Message)); } return(null); }
public static int Execute(List <string> args) { if (args.Count < 1) { Console.WriteLine("Usage: infile.txt [outfile.bps]"); return(-1); } string inpath = args[0]; string outpath = args.Count >= 2 ? args[1] : (inpath + ".bps"); using (var instream = new HyoutaUtils.Streams.DuplicatableFileStream(inpath)) using (var outstream = new FileStream(outpath, FileMode.Create)) { TextToBpsConverter.GeneratePatchFromText(instream, outstream); } return(0); }
// this is not a generic wav parser, this just works for diffing two already similar files public wav_samples(string filename) { using (var fs = new HyoutaUtils.Streams.DuplicatableFileStream(filename)) { var e = EndianUtils.Endianness.LittleEndian; uint riff = fs.ReadUInt32(e); uint rifflength = fs.ReadUInt32(e); uint wave = fs.ReadUInt32(e); uint fmt = fs.ReadUInt32(e); uint fmt_length = fs.ReadUInt32(e); ushort format = fs.ReadUInt16(e); ushort channels = fs.ReadUInt16(e); uint samplerate = fs.ReadUInt32(e); uint bytes_per_second = fs.ReadUInt32(e); ushort block_align = fs.ReadUInt16(e); ushort bits_per_sample = fs.ReadUInt16(e); uint data = fs.ReadUInt32(e); uint data_length = fs.ReadUInt32(e); if (channels == 1) { long sample_count = data_length / 2; short[] samples = new short[sample_count]; for (long i = 0; i < sample_count; ++i) { samples[i] = fs.ReadInt16(e); } Samples_L = samples; } else if (channels == 2) { long sample_count = data_length / 4; short[] samples_left = new short[sample_count]; short[] samples_right = new short[sample_count]; for (long i = 0; i < sample_count; ++i) { samples_left[i] = fs.ReadInt16(e); samples_right[i] = fs.ReadInt16(e); } Samples_L = samples_left; Samples_R = samples_right; } } }
public static int ExecuteCreate(List <string> args) { if (args.Count < 3) { Console.WriteLine("Usage: source.bin target.bin patch.bps"); return(-1); } string sourcepath = args[0]; string targetpath = args[1]; string patchpath = args[2]; using (var sourcestream = new HyoutaUtils.Streams.DuplicatableFileStream(sourcepath)) using (var targetstream = new HyoutaUtils.Streams.DuplicatableFileStream(targetpath)) using (var outstream = new FileStream(patchpath, FileMode.Create)) { CreateSimplest.CreatePatch(sourcestream, targetstream, outstream); } return(0); }
public static int Main(string[] args) { if (args.Length >= 2 && (args[0] == "--parse-script" || args[0] == "--parse-book")) { using (var fs = new HyoutaUtils.Streams.DuplicatableFileStream(args[1])) { var funcs = ScriptParser.Parse(fs.CopyToByteArrayStreamAndDispose(), args[0] == "--parse-book"); using (var outfs = new FileStream(args.Length > 2 ? args[2] : args[1] + ".txt", FileMode.Create)) { foreach (var func in funcs) { outfs.WriteUTF8(func.Name); outfs.WriteUTF8("\n"); outfs.WriteUTF8("\n"); foreach (var op in func.Ops) { outfs.WriteUTF8(op); outfs.WriteUTF8("\n"); } outfs.WriteUTF8("\n"); outfs.WriteUTF8("\n"); } } } return(0); } if (args.Length == 1 && args[0] == "__gen_voice_checks") { t_voice_tbl.CheckVoiceTable( Path.Combine(SenCommonPaths.Sen1SteamDir, "data/text/dat_us/t_voice.tbl"), Path.Combine(SenCommonPaths.Sen1SteamDir, "data/voice/wav"), Path.Combine(SenCommonPaths.Sen1SteamDir, "voice_check_english.txt") ); t_voice_tbl.CheckVoiceTable( Path.Combine(SenCommonPaths.Sen1SteamDir, "data/text/dat/t_voice.tbl"), Path.Combine(SenCommonPaths.Sen1SteamDir, "data/voice/wav_jp"), Path.Combine(SenCommonPaths.Sen1SteamDir, "voice_check_japanese.txt") ); t_voice_tbl.CheckVoiceTable( Path.Combine(SenCommonPaths.Sen2SteamDir, "data/text/dat_us/t_voice.tbl"), Path.Combine(SenCommonPaths.Sen2SteamDir, "data/voice/wav"), Path.Combine(SenCommonPaths.Sen2SteamDir, "voice_check_english.txt") ); t_voice_tbl.CheckVoiceTable( Path.Combine(SenCommonPaths.Sen2SteamDir, "data/text/dat/t_voice.tbl"), Path.Combine(SenCommonPaths.Sen2SteamDir, "data/voice_jp/wav"), Path.Combine(SenCommonPaths.Sen2SteamDir, "voice_check_japanese.txt") ); return(0); } if (args.Length == 0) { Console.WriteLine("No path to directory given."); return(-1); } string path = args[0]; if (!Directory.Exists(path)) { Console.WriteLine($"No directory found at {path}."); return(-1); } int sengame; if (File.Exists(System.IO.Path.Combine(path, "Sen1Launcher.exe"))) { sengame = 1; } else if (File.Exists(System.IO.Path.Combine(path, "Sen2Launcher.exe"))) { sengame = 2; } else { Console.WriteLine($"Failed to detect whether {path} is CS1 or 2."); return(-1); } FilenameFix.FixupIncorrectEncodingInFilenames(path, sengame, true, new CliProgressReporter()); FileStorage storage = FileModExec.InitializeAndPersistFileStorage(path, sengame == 1 ? Sen1KnownFiles.Files : Sen2KnownFiles.Files, new CliProgressReporter())?.Storage; if (storage == null) { Console.WriteLine($"Failed to initialize file storage from {path}."); return(-1); } PatchResult result; if (sengame == 1) { var mods = new List <FileMod>(); mods.AddRange(Sen1Mods.GetExecutableMods( removeTurboSkip: true, allowR2NotebookShortcut: true, turboKey: 0xA, fixTextureIds: true, correctLanguageVoiceTables: true, disableMouseCapture: true, disablePauseOnFocusLoss: true )); mods.AddRange(Sen1Mods.GetAssetMods()); result = FileModExec.ExecuteMods(path, storage, mods, new CliProgressReporter()); } else { var mods = new List <FileMod>(); mods.AddRange(Sen2Mods.GetExecutableMods( removeTurboSkip: true, patchAudioThread: true, audioThreadDivisor: 1000, patchBgmQueueing: true, correctLanguageVoiceTables: true, disableMouseCapture: true, disablePauseOnFocusLoss: true )); mods.AddRange(Sen2Mods.GetAssetMods()); result = FileModExec.ExecuteMods(path, storage, mods, new CliProgressReporter()); } if (!result.AllSuccessful) { Console.WriteLine($"Failed to patch CS{sengame} at {path}."); return(-1); } Console.WriteLine($"Successfully patched CS{sengame} at {path}."); return(0); }
public void Cs1GameInit() { int CurrentProgress = 0; int TotalProgress = 4; bool shouldAutoCloseWindow = true; try { Progress.Message("Checking Sen1Launcher.exe...", CurrentProgress++, TotalProgress); using (var fs = new HyoutaUtils.Streams.DuplicatableFileStream(Sen1LauncherPath)) { SHA1 hash = ChecksumUtils.CalculateSHA1ForEntireStream(fs); if (hash != new SHA1(0x8dde2b39f128179aul, 0x0beb3301cfd56a98ul, 0xc0f98a55u)) { Progress.Error("Selected file does not appear to be Sen1Launcher.exe of version 1.6."); Progress.Finish(false); return; } } } catch (Exception ex) { Progress.Error("Error while validating Sen1Launcher.exe: " + ex.Message); Progress.Finish(false); return; } try { Path = System.IO.Path.GetDirectoryName(Sen1LauncherPath); Progress.Message("Checking if we have encoding errors in filenames...", CurrentProgress++, TotalProgress); if (FilenameFix.FixupIncorrectEncodingInFilenames(Path, 1, false, Progress)) { if (!FilenameFix.FixupIncorrectEncodingInFilenames(Path, 1, true, Progress)) { Progress.Error("Failed to fix encoding errors in filenames, attempting to proceed anyway..."); shouldAutoCloseWindow = false; } } Progress.Message("Initializing patch data...", CurrentProgress++, TotalProgress); var files = Sen1KnownFiles.Files; Progress.Message("Initializing game data...", CurrentProgress++, TotalProgress); var storageInit = FileModExec.InitializeAndPersistFileStorage(Path, files, Progress); Storage = storageInit?.Storage; if (storageInit == null || storageInit.Errors.Count != 0) { shouldAutoCloseWindow = false; } } catch (Exception ex) { Progress.Error("Error while initializing CS1 patch/game data: " + ex.Message); Progress.Finish(false); return; } ShouldProceedToPatchOptionWindow = Path != null && Storage != null; if (shouldAutoCloseWindow) { Progress.Message("Initialized CS1 data, proceeding to patch options...", CurrentProgress, TotalProgress); } else { Progress.Message("", CurrentProgress, TotalProgress); if (ShouldProceedToPatchOptionWindow) { Progress.Error( "Encountered problems while initializing CS1 data. " + "Closing this window will proceed to the patch options anyway, but be aware that some patches may not work correctly. " + "It is recommended to verify the game files using Steam or GOG Galaxy's build-in feature to do so, or to reinstall the game. " + "Please also ensure you're trying to patch a compatible version of the game. (XSEED release version 1.6; other game versions are not compatible)" ); } else { Progress.Error( "Unrecoverable issues while initializing CS1 data. " + "Please ensure SenPatcher has read and write access to the selected game directory, then try again." ); } } Progress.Finish(shouldAutoCloseWindow); }
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); } }