diff --git a/.gitignore b/.gitignore index fac085f..a7a1745 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ rooms rooms_out +bappackage.exe +bappackage-*.exe +run.bat diff --git a/assignroom.go b/assignroom.go index f346d7e..3ffde12 100644 --- a/assignroom.go +++ b/assignroom.go @@ -4,22 +4,22 @@ func AssignExtraRooms(rooms []RoomFolder, roomsPerNight int) { latestnight := 0 for _, r := range rooms { - if r.cfg.night > latestnight { - latestnight = r.cfg.night + if r.Cfg.night > latestnight { + latestnight = r.Cfg.night } } - + night := latestnight + 1 room := 1 for i, r := range rooms { - if r.cfg.night > 0 { + if r.Cfg.night > 0 { continue } - rooms[i].cfg.night = night - rooms[i].cfg.room = room - + rooms[i].Cfg.night = night + rooms[i].Cfg.room = room + room++ if room > roomsPerNight { room = 1 diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..86bc441 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,6 @@ +Remove-Item .\bappackage-*.exe -ErrorAction SilentlyContinue +Remove-Item .\bappackage.exe -ErrorAction SilentlyContinue +go build . +$hash = (Get-FileHash -Algorithm SHA256 .\bappackage.exe).Hash +$hash = $hash.Remove(0, ($hash.Length - 8)) +Move-Item .\bappackage.exe .\bappackage-$hash.exe diff --git a/gameinfo.go b/gameinfo.go new file mode 100644 index 0000000..1f814ca --- /dev/null +++ b/gameinfo.go @@ -0,0 +1,68 @@ +package main + +import "slices" + +var REQUIRED_IMAGES = []string{ + "ART", + "CREDIT", + "FLOOR", + "ROOF", + "WALL", +} +var OPTIONAL_BUTREQUIRED_IMAGES = []string{ + "MINI", + "INTERACT", + "LAYER", +} +var OPTIONAL_IMAGES = []string{ + "WALL2", + "WALL3", + "WALL4", +} +var ACCEPTED_IMAGE_EXT = []string{ + "PNG", + "JPG", + "BMP", +} + +var OPTIONAL_BUTREQUIRED_AUDIO = []string{ + "FOOT", + "MUSIC", +} +var ACCEPTED_AUDIO_EXT = []string{ + "WAV", + "MP3", + "OGG", + "MIDI", +} + +func gameRequiredImages() []string { + minimal := make([]string, len(REQUIRED_IMAGES)) + copy(minimal, REQUIRED_IMAGES) + return minimal +} + +func gameExtraRequiredImages() []string { + extra := make([]string, len(OPTIONAL_BUTREQUIRED_IMAGES)) + copy(extra, OPTIONAL_BUTREQUIRED_IMAGES) + return extra +} + +func gameExtraRequiredAudios() []string { + extra := make([]string, len(OPTIONAL_BUTREQUIRED_AUDIO)) + copy(extra, OPTIONAL_BUTREQUIRED_AUDIO) + return extra +} + +func isAcceptedImageExt(ext string) bool { + return slices.Contains(ACCEPTED_IMAGE_EXT, ext) +} + +func isAcceptedAudioExt(ext string) bool { + return slices.Contains(ACCEPTED_AUDIO_EXT, ext) +} + +func isAcceptedFile(name, ext string) bool { + return (isAcceptedImageExt(ext) && (slices.Contains(REQUIRED_IMAGES, name) || slices.Contains(OPTIONAL_BUTREQUIRED_IMAGES, name) || slices.Contains(OPTIONAL_IMAGES, name))) || + (isAcceptedAudioExt(ext) && slices.Contains(OPTIONAL_BUTREQUIRED_AUDIO, name)) +} diff --git a/ini.go b/ini.go index b751d57..908e3f9 100644 --- a/ini.go +++ b/ini.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "log" "strconv" "gopkg.in/ini.v1" @@ -9,17 +10,17 @@ import ( type RoomIni struct { night int - room int + room int - size float64 - speed float64 - fov float64 + size float64 + speed float64 + fov float64 textureRepeat float64 - roomHeight float64 - layerFrames int - fogColor int - fogEnd float64 - miniSpeed float64 + roomHeight float64 + layerFrames int + fogColor int + fogEnd float64 + miniSpeed float64 } func (r *RoomIni) ToIni() string { @@ -39,29 +40,29 @@ Fog_Color=%d Fog_End=%s Mini_speed=%s `, - r.night, r.room, - f(r.size), f(r.speed), - f(r.fov), f(r.textureRepeat), - f(r.roomHeight), r.layerFrames, - r.fogColor, f(r.fogEnd), - f(r.miniSpeed), -) + r.night, r.room, + f(r.size), f(r.speed), + f(r.fov), f(r.textureRepeat), + f(r.roomHeight), r.layerFrames, + r.fogColor, f(r.fogEnd), + f(r.miniSpeed), + ) } func NewRoomIni() RoomIni { - return RoomIni { + return RoomIni{ night: -1, - room: -1, + room: -1, - size: 1, - speed: 1, - fov: 55, + size: 1, + speed: 1, + fov: 55, textureRepeat: 3, - roomHeight: 350, - layerFrames: 1, - fogColor: 0, - fogEnd: -1, - miniSpeed: 1, + roomHeight: 350, + layerFrames: 1, + fogColor: 0, + fogEnd: -1, + miniSpeed: 1, } } @@ -71,7 +72,6 @@ func ReadIni(path string) (RoomIni, error) { return RoomIni{}, err } - meta, err := cfg.GetSection("DEFAULT") if err != nil { return RoomIni{}, fmt.Errorf("unable to load DEFAULT section in %s: %+v", path, err) @@ -82,7 +82,7 @@ func ReadIni(path string) (RoomIni, error) { for _, sec := range sections { if room != nil { - fmt.Printf("WARN: extra section in %s, section: %s\n", path, sec.Name()) + log.Printf("WARN: extra section in %s, section: %s", path, sec.Name()) continue } if sec.Name() != "default" { @@ -117,7 +117,6 @@ func loadIni(meta *ini.Section, room *ini.Section) (RoomIni, error) { return RoomIni{}, fmt.Errorf("night is set but room is not set") } - if val, err := loatFloat(room, "size", 1); err != nil { return RoomIni{}, err } else { diff --git a/loadrooms.go b/loadrooms.go index 0074101..13480b5 100644 --- a/loadrooms.go +++ b/loadrooms.go @@ -2,43 +2,16 @@ package main import ( "fmt" + "log" "os" - "path" "path/filepath" "slices" - "strings" ) -var REQUIRED_IMAGES = []string{ - "ART", - "MINI", - "CREDIT", - "INTERACT", - "FLOOR", - "LAYER", - "ROOF", - "WALL", -} -var ACCEPTED_IMAGE_EXT = []string{ - ".PNG", - ".JPG", - ".BMP", -} - -var REQUIRED_AUDIO = []string{ - "FOOT", - "MUSIC", -} -var ACCEPTED_AUDIO_EXT = []string{ - ".WAV", - ".MP3", - ".OGG", - ".MIDI", -} - type RoomFolder struct { - path string - cfg RoomIni + Path string + Cfg RoomIni + IsDefaultCfg bool } func LoadRoomFolders(roomspath string) ([]RoomFolder, error) { @@ -50,15 +23,14 @@ func LoadRoomFolders(roomspath string) ([]RoomFolder, error) { rooms := make([]RoomFolder, 0) for _, entry := range entries { - entrypath := filepath.Join(roomspath, entry.Name()) - if !entry.IsDir() { - fmt.Printf("WARN: unexpected non-directory found in rooms directory: %s\n", entrypath) + if entry.Name()[0] == '.' || !entry.IsDir() { continue } + entrypath := filepath.Join(roomspath, entry.Name()) roomfolder, err := loadRoom(entrypath) if err != nil { - fmt.Printf("ERROR: unable to load directory: %s\n", entrypath) + log.Printf("ERROR: unable to load directory: %s: %v", entrypath, err) continue } @@ -74,58 +46,43 @@ func loadRoom(roompath string) (RoomFolder, error) { return RoomFolder{}, err } - requiredImages := make([]string, len(REQUIRED_IMAGES)) - copy(requiredImages, REQUIRED_IMAGES) - requiredAudio := make([]string, len(REQUIRED_AUDIO)) - copy(requiredAudio, REQUIRED_AUDIO) + minimalRequiredImages := gameRequiredImages() roominiName := "" for _, entry := range entries { - filename := entry.Name() - if strings.ToLower(filename) == "room.ini" { - roominiName = filename + if entry.Name()[0] == '.' || entry.IsDir() { + continue } - ext := strings.ToUpper(path.Ext(filename)) - name := strings.ToUpper(filename[:len(filename)-len(ext)]) - - if index := slices.Index(requiredImages, name); index > -1 && slices.Contains(ACCEPTED_IMAGE_EXT, ext) { - requiredImages = append(requiredImages[:index], requiredImages[index+1:]...) + name, ext := SplitExt(entry.Name()) + + if name == "ROOM" && (ext == "INI" || ext == "TXT") { + roominiName = entry.Name() } - - if index := slices.Index(requiredAudio, name); index > -1 && slices.Contains(ACCEPTED_AUDIO_EXT, ext) { - requiredAudio = append(requiredAudio[:index], requiredAudio[index+1:]...) + + if index := slices.Index(minimalRequiredImages, name); index > -1 && isAcceptedImageExt(ext) { + minimalRequiredImages = append(minimalRequiredImages[:index], minimalRequiredImages[index+1:]...) } } - errormsg := "" - if len(requiredImages) > 0 && len(requiredAudio) > 0 { - errormsg = fmt.Sprintf("images: %v, audio: %v", requiredImages, requiredAudio) - } - if len(requiredImages) > 0 { - errormsg = fmt.Sprintf("images: %v", requiredImages) - } - if len(requiredAudio) > 0 { - errormsg = fmt.Sprintf("audio: %v", requiredAudio) - } - - if errormsg != "" { - return RoomFolder{}, fmt.Errorf("unable to load room %s: missing required %s", roompath, errormsg) + if len(minimalRequiredImages) > 0 { + return RoomFolder{}, fmt.Errorf("unable to load room %s: missing required images: %v", roompath, minimalRequiredImages) } cfg := NewRoomIni() + defaultCfg := true if roominiName != "" { - newcfg, err := ReadIni(filepath.Join(roompath, "room.ini")) + readcfg, err := ReadIni(filepath.Join(roompath, roominiName)) if err != nil { return RoomFolder{}, err } - cfg = newcfg + cfg = readcfg + defaultCfg = false } return RoomFolder{ - path: roompath, - cfg: cfg, + Path: roompath, + Cfg: cfg, + IsDefaultCfg: defaultCfg, }, nil } - - diff --git a/main.go b/main.go index 4dcc925..4eec1a5 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,6 @@ package main -const BASEPATH = "./rooms" -const OUTPATH = "./rooms_out" -const ROOM_PER_NIGHT = 4 +import "flag" func main() { if err := mainErr(); err != nil { @@ -11,12 +9,27 @@ func main() { } func mainErr() error { - roomFolders, err := LoadRoomFolders(BASEPATH) + basePath := flag.String("basePath", "./rooms", "path to your rooms collection") + outPath := flag.String("outPath", "./rooms_out", "output path") + roomPerNight := flag.Int("roomPerNight", 4, "rooms per night") + + splash1len := flag.Int("splash1", 2, "number of seconds splashscreen 1 will take") + splash2len := flag.Int("splash2", 2, "number of seconds splashscreen 2 will take") + + flag.Parse() + + roomFolders, err := LoadRoomFolders(*basePath) if err != nil { return err } - AssignExtraRooms(roomFolders, ROOM_PER_NIGHT) + AssignExtraRooms(roomFolders, *roomPerNight) - return CopyOut(roomFolders, OUTPATH) + // only deletes DATA/NIGHT_* folders and the CF.ini file + err = EmptyOut(*outPath) + if err != nil { + return err + } + + return CopyOut(roomFolders, *outPath, *splash1len, *splash2len) } diff --git a/out.go b/out.go index 4ca6f96..18ef4d7 100644 --- a/out.go +++ b/out.go @@ -1,74 +1,183 @@ 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 -const INIPREFIX = `[SETTINGS] -Sensitivity=6 -Splash1len=2 -Splash2len=2 -` -func CopyOut(roomFolders []RoomFolder, outpath string) error { - inistr := INIPREFIX - - for _, roomFolder := range roomFolders { - err := copyFolder(roomFolder, outpath) - if err != nil { - return fmt.Errorf("error copying folder %s: %+v", roomFolder.path, err) - } - inistr += roomFolder.cfg.ToIni() - } - - outini := filepath.Join(outpath, "CF.ini") - fmt.Printf("Writing %s\n", outini) - return os.WriteFile(outini, []byte(inistr), FILEPERM) -} - -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) - fmt.Printf(`Copying: - %s - to %s -`, roomFolder.path, roomoutpath) - - entries, err := os.ReadDir(roomFolder.path) +// 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 } + 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 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 { - // TODO whitelist files? - entrypath := path.Join(roomFolder.path, entry.Name()) - - if entry.IsDir() { - fmt.Printf("WARN: unexpected folder found in room folder: %s\n", entrypath) + if entry.Name()[0] == '.' || !entry.IsDir() { continue } - if strings.ToLower(entry.Name()) == "room.ini" { + 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 CopyOut(roomFolders []RoomFolder, outpath string, splash1len, splash2len int) error { + inistr := fmt.Sprintf(`[SETTINGS] +Sensitivity=6 +Splash1len=%d +Splash2len=%d +`, splash1len, splash2len) + + for _, roomFolder := range roomFolders { + err := copyFolder(roomFolder, outpath) + if err != nil { + return fmt.Errorf("error copying folder %s: %+v", roomFolder.Path, err) + } + inistr += roomFolder.Cfg.ToIni() + } + + outini := filepath.Join(outpath, "CF.ini") + log.Println("INFO:") + log.Printf("INFO: Writing %s", outini) + return os.WriteFile(outini, []byte(inistr), FILEPERM) +} + +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(entrypath, topath) + 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}) + + for _, imagename := range extraRequiredImages { + imagepath := path.Join(roomoutpath, imagename+".PNG") + + f, err := os.Create(imagepath) + if err != nil { + return err + } + png.Encode(f, transparent) + } + } + + 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 } diff --git a/silence.wav b/silence.wav new file mode 100644 index 0000000..24d262a Binary files /dev/null and b/silence.wav differ diff --git a/util.go b/util.go new file mode 100644 index 0000000..a9bb00b --- /dev/null +++ b/util.go @@ -0,0 +1,29 @@ +package main + +import ( + "errors" + "io/fs" + "os" + "path" + "strings" +) + +func Exists(p ...string) (bool, error) { + _, err := os.Stat(path.Join(p...)) + if err == nil { + return true, nil + } + if errors.Is(err, fs.ErrNotExist) { + return false, nil + } + return false, err +} + +func SplitExt(filename string) (string, string) { + ext := strings.ToUpper(path.Ext(filename)) + name := strings.ToUpper(filename[:len(filename)-len(ext)]) + if len(ext) > 0 { + ext = ext[1:] + } + return name, ext +}