package main import ( _ "embed" "fmt" "image" "image/color" "image/png" "log" "os" "path" "path/filepath" "slices" "strings" ) //go:embed silence.wav var SILENCE_WAV []byte const FILEPERM = 0644 // only deletes DATA/NIGHT_* folders and the CF.ini file // will not touch TITLE or any game files // so this can be ran safely in your game's directory func EmptyOut(outpath string) error { err := emptyOutData(outpath) if err != nil { return err } err = emptyOutCredits(outpath) if err != nil { return err } return emptyOutCF(outpath) } func emptyOutData(outpath string) error { datapath := path.Join(outpath, "DATA") exists, err := Exists(datapath) if err != nil { return err } if !exists { return nil } entries, err := os.ReadDir(datapath) if err != nil { return err } nightprefix := "NIGHT_" for _, entry := range entries { if entry.Name()[0] == '.' || !entry.IsDir() { continue } name := strings.ToUpper(entry.Name()) if len(name) >= len(nightprefix) && name[:len(nightprefix)] == nightprefix { nightpath := path.Join(datapath, entry.Name()) log.Printf("INFO: removing %s", nightpath) err = os.RemoveAll(nightpath) if err != nil { return err } } } return nil } func emptyOutCredits(outpath string) error { cfpath := path.Join(outpath, "credits.txt") exists, err := Exists(cfpath) if err != nil { return err } if !exists { return nil } log.Printf("INFO: removing %s", cfpath) return os.Remove(cfpath) } func emptyOutCF(outpath string) error { cfpath := path.Join(outpath, "CF.ini") exists, err := Exists(cfpath) if err != nil { return err } if !exists { return nil } log.Printf("INFO: removing %s", cfpath) return os.Remove(cfpath) } func CopyOut(roomFolders []RoomFolder, outpath string, splash1len, splash2len int) error { // base ini settings var inistr strings.Builder fmt.Fprintf(&inistr, `[SETTINGS] Sensitivity=6 Splash1len=%d Splash2len=%d `, splash1len, splash2len) // loop through folders roomcredits := []string{} for _, roomFolder := range roomFolders { err := copyFolder(&roomFolder, outpath) if err != nil { return fmt.Errorf("error copying folder %s: %+v", roomFolder.Path, err) } inistr.WriteString(roomFolder.Cfg.ToIni()) err = checkCredits(&roomFolder); if err != nil { return fmt.Errorf("error checking credits in folder %s: %+v", roomFolder.Path, err) } roomcredits = append(roomcredits, roomFolder.Cfg.ToCredits()) } // save ini file outini := filepath.Join(outpath, "CF.ini") log.Println("INFO:") log.Printf("INFO: Writing %s", outini) if err := os.WriteFile(outini, []byte(inistr.String()), FILEPERM); err != nil { return err } // save credits file creditsstring := strings.Join(roomcredits, "\n") outcredits := filepath.Join(outpath, "credits.txt") log.Println("INFO:") log.Printf("INFO: Writing %s", outcredits) return os.WriteFile(outcredits, []byte(creditsstring), FILEPERM) } // doesn't error currently func checkCredits(roomFolder *RoomFolder) error { if roomFolder.Cfg.name == "" { log.Println("WARNING: room config has no name, defaulting to folder name") foldername := filepath.Base(roomFolder.Path) roomFolder.Cfg.name = foldername } if roomFolder.Cfg.credits == "" { log.Println("WARNING: room config has no credits") } return nil } func copyFolder(roomFolder *RoomFolder, outpath string) error { nightdir := fmt.Sprintf("NIGHT_%d", roomFolder.Cfg.night) roomdir := fmt.Sprintf("ROOM_%d", roomFolder.Cfg.room) roomoutpath := filepath.Join(outpath, "DATA", nightdir, roomdir) os.MkdirAll(roomoutpath, FILEPERM) log.Println("INFO:") log.Printf("INFO: Copying %s", roomFolder.Path) log.Printf("INFO: to %s", roomoutpath) if roomFolder.IsDefaultCfg { log.Println("INFO: room.ini was not found, using default settings") } entries, err := os.ReadDir(roomFolder.Path) if err != nil { return err } extraRequiredImages := gameExtraRequiredImages() extraRequiredAudio := gameExtraRequiredAudios() for _, entry := range entries { if entry.Name()[0] == '.' || entry.IsDir() { continue } name, ext := SplitExt(entry.Name()) if !isAcceptedFile(name, ext) { continue } if index := slices.Index(extraRequiredImages, name); index > -1 && isAcceptedImageExt(ext) { extraRequiredImages = append(extraRequiredImages[:index], extraRequiredImages[index+1:]...) } if index := slices.Index(extraRequiredAudio, name); index > -1 && isAcceptedAudioExt(ext) { extraRequiredAudio = append(extraRequiredAudio[:index], extraRequiredAudio[index+1:]...) } frompath := path.Join(roomFolder.Path, entry.Name()) topath := path.Join(roomoutpath, entry.Name()) err := copyFile(frompath, topath) if err != nil { return err } } if len(extraRequiredImages) > 0 { log.Printf("INFO: necessary optional images will use a transparent file: %v", extraRequiredImages) transparent := image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{1, 1}}) transparent.Set(0, 0, color.RGBA{0xff, 0xff, 0xff, 0x01}) transparentminifig := image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{4, 1}}) transparentminifig.Set(0, 0, color.RGBA{0xff, 0xff, 0xff, 0x01}) transparentminifig.Set(1, 0, color.RGBA{0xff, 0xff, 0xff, 0x01}) transparentminifig.Set(2, 0, color.RGBA{0xff, 0xff, 0xff, 0x01}) transparentminifig.Set(3, 0, color.RGBA{0xff, 0xff, 0xff, 0x01}) for _, imagename := range extraRequiredImages { imagepath := path.Join(roomoutpath, imagename+".png") f, err := os.Create(imagepath) if err != nil { return err } if strings.ToUpper(imagename) == "MINI" { png.Encode(f, transparentminifig) } else { png.Encode(f, transparent) } // ensure layer_frames is 1 if it's not included if strings.ToUpper(imagename) == "LAYER" && roomFolder.Cfg.layerFrames != 1 { log.Printf( "WARNING: room was configured to have %d layer frames, but no LAYER image exists", roomFolder.Cfg.layerFrames) log.Println("WARNING: setting layer_frames=1") roomFolder.Cfg.layerFrames = 1 } } } if len(extraRequiredAudio) > 0 { log.Printf("INFO: necessary optional audios will use a silent file: %v", extraRequiredAudio) for _, audioname := range extraRequiredAudio { audiopath := path.Join(roomoutpath, audioname+".wav") err := os.WriteFile(audiopath, SILENCE_WAV, FILEPERM) if err != nil { return err } } } return nil } func copyFile(from, to string) error { fromdata, err := os.ReadFile(from) if err != nil { return err } return os.WriteFile(to, fromdata, FILEPERM) }