improved file read/copy logic

This commit is contained in:
zomo
2025-12-24 13:58:02 -06:00
parent 658ad890f5
commit 32ed104b60
10 changed files with 337 additions and 153 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,5 @@
rooms
rooms_out
bappackage.exe
bappackage-*.exe
run.bat

View File

@@ -4,8 +4,8 @@ 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
}
}
@@ -13,12 +13,12 @@ func AssignExtraRooms(rooms []RoomFolder, roomsPerNight int) {
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 {

6
build.ps1 Normal file
View File

@@ -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

68
gameinfo.go Normal file
View File

@@ -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))
}

57
ini.go
View File

@@ -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 {

View File

@@ -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)])
name, ext := SplitExt(entry.Name())
if index := slices.Index(requiredImages, name); index > -1 && slices.Contains(ACCEPTED_IMAGE_EXT, ext) {
requiredImages = append(requiredImages[:index], requiredImages[index+1:]...)
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
}

25
main.go
View File

@@ -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)
}

193
out.go
View File

@@ -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
}

BIN
silence.wav Normal file

Binary file not shown.

29
util.go Normal file
View File

@@ -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
}