void Start() { m_CurrLvlUrl = GameSettings.Instance.LevelInt; m_DataInstance = this; }
/// /// The primary code block that turns the custom level data into a format the game can read /// private void Make_Custom_Levels(string game_dir) { int menulength = 2112; List <string> src_filenames = new List <string>() { "lib/2e7b0500.pc", "lib/e0c51024.pc", "lib/f78b7d78.pc" }; //these hashes are literally "customlevel#" hashed List <string> menu_hashes = new List <string>() { "1DCB06CE", "2D5C3C41", "273EA275", "EBA1CBD7", "1F8AD438", "DDF57F91", "9402A958", "FB3C6A42", "D4F0FAD2" }; List <string> menu_names = new List <string>(); //clear \out\ directory so that old level data is not stored anymore DirectoryInfo _out = new DirectoryInfo(@"out"); foreach (string file in Directory.EnumerateFiles(@"out", "*.*", SearchOption.AllDirectories)) { File.Delete(file); } foreach (DirectoryInfo dir in _out.GetDirectories()) { dir.Delete(); } //loop over each selected level foreach (LevelTraits level_name in LoadedLevels) { dynamic level_config = null; var objs = new List <dynamic>(); var obj_count = 0; dynamic new_objs = null; string errorlist = ""; //iterate over each file in the custom level directory //filter out files that do not match the <file_types> list foreach (string filename in Directory.GetFiles(level_name.path)) { if (file_types.Contains(Path.GetFileName(filename).Split('_')[0])) { //read file and store JSON in dynamic object try { new_objs = JObject.Parse(Regex.Replace(File.ReadAllText(filename), @"#.*", ""), jo); } catch (Exception ex) { errorlist += $"error parsing:\n{ex.Message} in file \"{Path.GetFileName(filename)}\" in level \"{level_name.name}\"\n\n"; continue; } //LevelLib contains important info for where the final level files go, so it gets put into its own object if (new_objs.obj_type == "LevelLib") { level_config = new_objs; obj_count--; } objs.Add(new_objs); obj_count++; } //these file types require different processing to get the data else if (file_special.Contains(Path.GetFileName(filename).Split('_')[0])) { try { new_objs = JsonConvert.DeserializeObject(File.ReadAllText(filename)); } catch (Exception ex) { errorlist += $"error parsing:\n{ex.Message} in file \"{Path.GetFileName(filename)}\" in level \"{level_name.name}\"\n\n"; continue; } //spn_ and samp_ files contain multiple entries, inside the "multi":[] list foreach (var _v in new_objs.items) { objs.Add(_v); obj_count++; } } } //if errors exist, show the error, then return to stop further processing if (errorlist.Contains("error parsing")) { MessageBox.Show(errorlist + "CUSTOM LEVELS NOT UPDATED", "Level load error"); return; } //var cache_filename = $@"out\{level_name.folder_name}\{level_config["cache_filename"]}"; var cache_filename = $@"out\{level_name.folder_name}\{list_cache_filename[LoadedLevels.IndexOf(level_name)]}"; Directory.CreateDirectory($@"out\{level_name.folder_name}"); byte[] bytes; using (FileStream f = File.Open(cache_filename, FileMode.Create, FileAccess.Write, FileShare.None)) { //write header information to level .pc file bytes = File.ReadAllBytes(@"lib/header.objlib"); f.Write(bytes, 0, bytes.Length); //write objlib path to level .pc file //Write_String(f, (string)level_config["objlib_path"]); Write_String(f, $"levels/custom/level{LoadedLevels.IndexOf(level_name)+1}.objlib"); //write "basic list of objects #1" information to level .pc file bytes = File.ReadAllBytes(@"lib/obj_list_1.objlib"); f.Write(bytes, 0, bytes.Length); //write to file the amount of objects that exists (after this number) //this includes everything in "obj_list_2.objlib" (63) and obj_count Write_Int(f, 63 + obj_count); //write "basic list of objects #2" information to level .pc file bytes = File.ReadAllBytes(@"lib/obj_list_2.objlib"); f.Write(bytes, 0, bytes.Length); //write every object to the .pc file, hashing its name foreach (var obj in objs) { if (obj_types.Contains((string)obj["obj_type"])) { if ((string)obj["obj_type"] != "Xfmer") { Write_Hash(f, (string)obj["obj_type"]); Write_String(f, (string)obj["obj_name"]); } else { Write_Hash(f, (string)obj["obj_type"]); Write_String(f, $"levels/custom/level{LoadedLevels.IndexOf(level_name) + 1}.xfm"); } } } //bytes = File.ReadAllBytes($@"lib/obj_def_customlevel{LoadedLevels.IndexOf(level_name) + 1}.objlib"); bytes = File.ReadAllBytes($@"lib/obj_def_customlevel.objlib"); f.Write(bytes, 0, bytes.Length); //iterate over every loaded object, and write its data to .pc file in specific formats. //format is different per object. I myself am not exactly sure how it works, but this is how it's done foreach (var obj in objs) { if (obj["obj_type"] == "SequinLeaf") { Write_Leaf(f, obj); } else if (obj["obj_type"] == "SequinLevel") { Write_Lvl_Header(f); Write_Approach_Anim_Comp(f, obj); Write_Lvl_Comp(f, obj); Write_Lvl_Footer(f, obj); } else if (obj["obj_type"] == "SequinGate") { Write_Gate(f, obj); } else if (obj["obj_type"] == "SequinMaster") { Write_Master(f, obj); } else if (obj["obj_type"] == "EntitySpawner") { Write_Spn(f, obj); } else if (obj["obj_type"] == "Sample") { Write_Samp(f, obj); } else if (obj["obj_type"] == "Xfmer") { Write_Xfm_Header(f); Write_Xfm_Comp(f, obj); } } //close out file with footer info #1 bytes = File.ReadAllBytes(@"lib/footer_1.objlib"); f.Write(bytes, 0, bytes.Length); //write file bpm Write_Float(f, (float)level_config["bpm"]); //close out file with footer info #2 bytes = File.ReadAllBytes(@"lib/footer_2.objlib"); f.Write(bytes, 0, bytes.Length); src_filenames.Add(cache_filename); } //var config_cache_filename = $@"out\{level_name.folder_name}\{level_config["config_cache_filename"]}"; var config_cache_filename = $@"out\{level_name.folder_name}\{list_config_cache_filename[LoadedLevels.IndexOf(level_name)]}"; using (FileStream f = File.Open(config_cache_filename, FileMode.Create, FileAccess.Write, FileShare.None)) { Write_Int(f, 9); Write_Int(f, level_config["level_sections"].Count); foreach (var level_section in level_config["level_sections"]) { Write_String(f, (string)level_section); } Write_Color(f, level_config["rails_color"]); Write_Color(f, level_config["rails_glow_color"]); Write_Color(f, level_config["path_color"]); Write_Color(f, level_config["joy_color"]); src_filenames.Add(config_cache_filename); } menu_names.Add(level_name.name); menulength += level_name.name.Length + 1; } //Update menu names using (FileStream f = File.Open(@"lib\2e7b0500.pc", FileMode.Create, FileAccess.Write, FileShare.None)) { byte[] bytes; int pos = 2112; //write menu headers/pointers Write_Int(f, 6); Write_Int(f, 174); Write_Int(f, menulength + ((9 - menu_names.Count) * ("no level".Length + 1))); //write all menu strings. Don't edit this bytes = File.ReadAllBytes(@"lib/menu1.objlib"); f.Write(bytes, 0, bytes.Length); //user set level names for (int x = 0; x < 9; x++) { if (x < menu_names.Count) { f.Write(Encoding.ASCII.GetBytes(menu_names[x]), 0, menu_names[x].Length); } else { f.Write(Encoding.ASCII.GetBytes("no level"), 0, "no level".Length); } f.Write(new byte[] { 0x00 }, 0, 1); } //write menu hashes and positions. Don't edit this bytes = File.ReadAllBytes(@"lib/menu2.objlib"); f.Write(bytes, 0, bytes.Length); //write custom hashes and string position in file. for (int x = 0; x < 9; x++) { Write_Hex(f, menu_hashes[x]); Write_Int(f, pos); if (x < menu_names.Count) { pos += menu_names[x].Length + 1; } else { pos += "no level".Length + 1; } } } //copy new .pc files to game directory foreach (string src_filename in src_filenames) { File.Copy(src_filename, $@"{game_dir}\cache\{Path.GetFileName(src_filename)}", true); } }