package main import ( _ "embed" "fmt" "image" "image/color" "image/png" "os" "path" "path/filepath" "slices" "strings" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/log" ) //go:embed silence.wav var SILENCE_WAV []byte const FILEPERM = 0644 var styleRedtext = lipgloss.NewStyle(). Foreground(lipgloss.Color("9")) var styleYellowtext = lipgloss.NewStyle(). Foreground(lipgloss.Color("11")) var styleGreentext = lipgloss.NewStyle(). Foreground(lipgloss.Color("10")) var padd = lipgloss.NewStyle(). PaddingLeft(4) // 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.Info(Renderf(styleRedtext, "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.Info(Renderf(styleRedtext, "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.Info(Renderf(styleRedtext, "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 log.Info("") 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()) log.Info("") } // save ini file outini := filepath.Join(outpath, "CF.ini") log.Info(Renderf(styleGreentext, "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.Info(Renderf(styleGreentext, "Writing %s", outcredits)) return os.WriteFile(outcredits, []byte(creditsstring), FILEPERM) } // doesn't error currently func checkCredits(roomFolder *RoomFolder) error { if roomFolder.Cfg.name == "" { log.Warn(padd.Render(styleYellowtext.Render("room config has no name, defaulting to folder name"))) foldername := filepath.Base(roomFolder.Path) roomFolder.Cfg.name = foldername } if roomFolder.Cfg.credits == "" { log.Warn(padd.Render(styleYellowtext.Render("room config has no credits"))) } return nil } var styleTextRoom = lipgloss.NewStyle(). Bold(true). Padding(0, 1). Background(lipgloss.Color("10")). Foreground(lipgloss.Color("0")) var styleTextCopy = lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("10")) 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) // print room info name := roomFolder.Cfg.name if name == "" { name = "MISSING NAME" } textRoom := Renderf(styleTextRoom, "Room - %s", name) log.Info(textRoom) textCopy1 := styleTextCopy.Render("Copying From") textCopy2 := styleTextCopy.Render(" To") textCopy1Comb := fmt.Sprintf("%s %s", textCopy1, roomFolder.Path) textCopy2Comb := fmt.Sprintf("%s %s", textCopy2, roomoutpath) log.Info(padd.Render(textCopy1Comb)) log.Info(padd.Render(textCopy2Comb)) if roomFolder.IsDefaultCfg { log.Info(padd.Render(styleYellowtext.Render("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.Info(Renderf(padd, "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.Warn(padd.Render(Renderf(styleYellowtext, "room was configured to have %d layer frames, but no LAYER image exists: setting layer_frames=1", roomFolder.Cfg.layerFrames))) roomFolder.Cfg.layerFrames = 1 } } } if len(extraRequiredAudio) > 0 { log.Info(Renderf(padd, "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) }