diff --git a/.gitignore b/.gitignore index 97e810c1d..526057188 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ # macOS .DS_Store +# Error files. +*.ERR + # CodeLite .CodeLite *.project @@ -15,6 +18,12 @@ devilution-comparer comparer-config.toml +# MPQ archives +*.mpq + +# Generated test data. +testdata + # ELF object file. *.o diff --git a/Makefile b/Makefile index fee594b36..08e1be4f6 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,9 @@ LDFLAGS=-L./ -static-libgcc -mwindows all: devilution.exe +testgen: CXXFLAGS += -DTESTGEN -I./SourceT -Wno-narrowing +testgen: testgen.exe + debug: CXXFLAGS += -D_DEBUG debug: CPPFLAGS += -D_DEBUG debug: devilution.exe @@ -28,12 +31,18 @@ debug: devilution.exe DIABLO_SRC=$(sort $(filter-out Source/_asm.cpp Source/_render.cpp, $(wildcard Source/*.cpp))) OBJS=$(DIABLO_SRC:.cpp=.o) +TESTGEN_SRC=SourceT/testgen.cpp +TESTGEN_OBJS=$(TESTGEN_SRC:.cpp=.o) + PKWARE_SRC=$(wildcard 3rdParty/PKWare/*.cpp) PKWARE_OBJS=$(PKWARE_SRC:.cpp=.o) devilution.exe: $(OBJS) $(PKWARE_OBJS) diabres.o diabloui.lib storm.lib $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS) +testgen.exe: $(OBJS) $(TESTGEN_OBJS) $(PKWARE_OBJS) diabres.o diabloui.lib storm.lib + $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS) + diabres.o: Diablo.rc $(WINDRES) $< $@ @@ -50,6 +59,6 @@ storm.dll: # $(error Please copy storm.dll (version 1.09[b]) here) clean: - @$(RM) -v $(OBJS) $(OBJS:.o=.d) $(PKWARE_OBJS) $(PKWARE_OBJS:.o=d) diabres.o storm.lib diabloui.lib devilution.exe + @$(RM) -v $(OBJS) $(TESTGEN_OBJS) $(OBJS:.o=.d) $(PKWARE_OBJS) $(PKWARE_OBJS:.o=d) diabres.o storm.lib diabloui.lib devilution.exe .PHONY: clean all diff --git a/MakefileTestgen b/MakefileTestgen new file mode 100644 index 000000000..3491bda96 --- /dev/null +++ b/MakefileTestgen @@ -0,0 +1,29 @@ +#TILES_BIN_NAME=dlvl=3,quest_id=12,seed=123,setlvl=0_tiles.bin +#TILES_BIN_PATH=testdata/${TILES_BIN_NAME} +#DUN_NAME=${TILES_BIN_NAME/.bin/.bin.dun} +#DUN_FROM=testdata/${DUN_NAME} +#DUN_TO=testdata/levels/l1data/${DUN_NAME} + +TILES_BIN=$(wildcard testdata/*_tiles.bin) +DUN_FROM=$(TILES_BIN:.bin=.bin.dun) +DUN_TO=$(subst testdata/,testdata/levels/l1data/,$(DUN_FROM)) + +all: $(DUN_TO) _dump_/_tilesets_ + +testdata/%.bin.dun: testdata/%.bin + ./cmd/viewdun/viewdun $< + +testdata/levels/l1data/%.bin.dun: testdata/%.bin.dun + mkdir -p testdata/levels/l1data + mv $< $@ + dun_dump -tilesets=false $@ + +_dump_/_tilesets_: + @echo "This step creates the tilesets and parses a dummy DUN file. It is expected to fail as the dummy file does not exist." + dun_dump -tilesets=true dummy + +clean: + $(RM) -v $(DUN_TO) + $(RM) -v $(DUN_FROM) + $(RM) -r -v _dump_/_tilesets_ + $(RM) -r -v _dump_/_dungeons_ diff --git a/MakefileVC b/MakefileVC index 0c768814a..c008ab29a 100644 --- a/MakefileVC +++ b/MakefileVC @@ -54,6 +54,9 @@ endif all: Diablo.exe +testgen: CFLAGS += /D "TESTGEN" /I ./Source /I ./SourceT +testgen: testgen.exe + debug: CFLAGS += /D "_DEBUG" debug: Diablo.exe @@ -62,9 +65,15 @@ DIABLO_SRC=$(sort $(filter-out Source/_asm.cpp Source/_render.cpp Source/render. DIABLO_SRC += Source/render.cpp OBJS=$(DIABLO_SRC:.cpp=.obj) +TESTGEN_SRC=$(sort $(wildcard SourceT/*.cpp)) +TESTGEN_OBJS=$(TESTGEN_SRC:.cpp=.obj) + Diablo.exe: main_files diablo.res DiabloUI/diabloui.lib 3rdParty/Storm/storm.lib 3rdParty/PKWare/pkware.lib $(VC_LINK) /OUT:$@ $(LINKFLAGS) $(OBJS) diablo.res DiabloUI/diabloui.lib 3rdParty/Storm/storm.lib kernel32.lib user32.lib gdi32.lib advapi32.lib shell32.lib version.lib 3rdParty/PKWare/pkware.lib +testgen.exe: main_files $(TESTGEN_OBJS) diablo.res DiabloUI/diabloui.lib 3rdParty/Storm/storm.lib 3rdParty/PKWare/pkware.lib + $(VC_LINK) /OUT:$@ $(LINKFLAGS) $(OBJS) $(TESTGEN_OBJS) diablo.res DiabloUI/diabloui.lib 3rdParty/Storm/storm.lib kernel32.lib user32.lib gdi32.lib advapi32.lib shell32.lib version.lib 3rdParty/PKWare/pkware.lib + DiabloUI/diabloui.lib: make -C DiabloUI @@ -85,6 +94,6 @@ diablo.res: Diablo.rc $(RC) /i $(VC6_INC_DIR) /l 0x409 /fo $@ $< clean: - @$(RM) -v $(OBJS) vc60.idb vc60.pdb Diablo.pdb Diablo.pch + @$(RM) -v $(OBJS) $(TESTGEN_OBJS) vc60.idb vc60.pdb Diablo.pdb Diablo.pch .PHONY: clean all diff --git a/Source/diablo.cpp b/Source/diablo.cpp index ade09cc6a..a7aeee644 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -252,6 +252,7 @@ BOOL diablo_get_not_running() * @param lpCmdLine The command line for the application * @param nCmdShow Initial window state */ +#ifndef TESTGEN int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HINSTANCE hInst; @@ -329,6 +330,7 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi return FALSE; } +#endif void diablo_parse_flags(char *args) { diff --git a/SourceT/main.cpp b/SourceT/main.cpp new file mode 100644 index 000000000..1e3e4d88c --- /dev/null +++ b/SourceT/main.cpp @@ -0,0 +1,9 @@ +#include "testgen.h" + +#include "all.h" + +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { + ghInst = hInstance; + testgen(); + return 0; +} diff --git a/SourceT/testgen.cpp b/SourceT/testgen.cpp new file mode 100644 index 000000000..0ddbc9c40 --- /dev/null +++ b/SourceT/testgen.cpp @@ -0,0 +1,246 @@ +#include +#include + +#include "all.h" + +void write_file(char *path, void *buf, int size); + +// drlg_l1.cpp + +#define SL_NONE 0 +#define Q_INVALID -1 + +void gen_drlg_l1_tests(void) { + printf("gen_drlg_l1_tests\n"); + + typedef struct { + // meta. + char *dungeon_name; + // pre. + BYTE dlvl; + BYTE dtype; + BYTE quest_id; + DWORD seed; + BYTE setlvl; + } Golden; + + // Golden test cases. + Golden golden[] = { + { + "Cathedral", // dungeon_name + 1, // dlvl + DTYPE_CATHEDRAL, // dtype + Q_INVALID, // quest_id + 123, // seed + SL_NONE, // setlvl + }, + { + "Cathedral (fix corners)", // dungeon_name + 1, // dlvl + DTYPE_CATHEDRAL, // dtype + Q_INVALID, // quest_id + 35, // seed + SL_NONE, // setlvl + }, + { + "The Butcher", // dungeon_name + questlist[Q_BUTCHER]._qdlvl, // dlvl + DTYPE_CATHEDRAL, // dtype + Q_BUTCHER, // quest_id + 123, // seed + SL_NONE, // setlvl + }, + { + "Poisoned Water Supply", // dungeon_name + questlist[Q_PWATER]._qdlvl, // dlvl + DTYPE_CATHEDRAL, // dtype + Q_PWATER, // quest_id + 123, // seed + SL_NONE, // setlvl + }, + { + "Ogden's Sign", // dungeon_name + questlist[Q_LTBANNER]._qdlvl, // dlvl + DTYPE_CATHEDRAL, // dtype + Q_LTBANNER, // quest_id + 123, // seed + SL_NONE, // setlvl + }, + { + "Skeleton King's Lair (entrance)", // dungeon_name + questlist[Q_SKELKING]._qdlvl, // dlvl + DTYPE_CATHEDRAL, // dtype + Q_SKELKING, // quest_id + 123, // seed + SL_NONE, // setlvl + }, + { + "Skeleton King's Lair", // dungeon_name + questlist[Q_SKELKING]._qdlvl, // dlvl + DTYPE_CATHEDRAL, // dtype + Q_SKELKING, // quest_id + 123, // seed + SL_SKELKING, // setlvl + }, + // Broken seeds. + { + "Cathedral (broken seed)", // dungeon_name + 1, // dlvl + DTYPE_CATHEDRAL, // dtype + Q_INVALID, // quest_id + 2588, // seed + SL_NONE, // setlvl + }, + { + "Cathedral (broken seed)", // dungeon_name + 1, // dlvl + DTYPE_CATHEDRAL, // dtype + Q_INVALID, // quest_id + 4743, // seed + SL_NONE, // setlvl + }, + { + "Cathedral (broken seed)", // dungeon_name + 1, // dlvl + DTYPE_CATHEDRAL, // dtype + Q_INVALID, // quest_id + 7281, // seed + SL_NONE, // setlvl + }, + { + "Cathedral (broken seed)", // dungeon_name + 1, // dlvl + DTYPE_CATHEDRAL, // dtype + Q_INVALID, // quest_id + 9345, // seed + SL_NONE, // setlvl + }, + { + "Cathedral (broken seed)", // dungeon_name + 1, // dlvl + DTYPE_CATHEDRAL, // dtype + Q_INVALID, // quest_id + 15236, // seed + SL_NONE, // setlvl + }, + // extra + { + "Cathedral (666)", // dungeon_name + 1, // dlvl + DTYPE_CATHEDRAL, // dtype + Q_INVALID, // quest_id + 1666, // seed + SL_NONE, // setlvl + }, + }; + + // extra. + int in_entry = 0; + BYTE in_max_players = 1; // single + + // Load MPQ archives. + init_archives(); + + // Load level graphics. + //diablo.LoadLevelGraphics(); + leveltype = DTYPE_CATHEDRAL; + LoadLvlGFX(); // TODO: only load level graphics once per dtype. + + for (int i = 0; i < sizeof(golden) / sizeof(Golden); i++) { + Golden g = golden[i]; + + // Reset globals (not needed). + memset(pdungeon, 0, sizeof(pdungeon)); + memset(dungeon, 0, sizeof(dungeon)); + memset(dPiece, 0, sizeof(dPiece)); + memset(dSpecial, 0, sizeof(dSpecial)); + memset(dTransVal, 0, sizeof(dTransVal)); + + //*gendung.DType = g.dtype; + leveltype = g.dtype; + + // Establish pre-conditions. + //*gendung.DLvl = g.dlvl; + currlevel = g.dlvl; + //*multi.MaxPlayers = 1; + gbMaxPlayers = in_max_players; + //for i := range quests.Quests { + // quests.Quests[i].ID = quests.QuestID(i); + // quests.Quests[i].Active = false; + //} + for (int quest_id = 0; quest_id < MAXQUESTS; quest_id++) { + quests[quest_id]._qtype = quest_id; + quests[quest_id]._qactive = false; + } + + //*gendung.IsQuestLevel = false; + if (g.setlvl != SL_NONE) { + setlevel = true; + setlvlnum = g.setlvl; + } else { + setlevel = false; + setlvlnum = SL_NONE; + } + + //if g.questID != quests.Invalid { + // quests.Quests[g.questID].Active = true; + // quests.Quests[g.questID].DLvl = g.dlvl; + //} + if (g.quest_id != Q_INVALID) { + quests[g.quest_id]._qlevel = g.dlvl; + quests[g.quest_id]._qactive = true; + } + + // Generate dungeon based on the given seed. + //l1.CreateDungeon(g.seed, 0); + + if (g.setlvl != SL_NONE) { + LoadSetMap(); + } else { + CreateL5Dungeon(g.seed, in_entry); + } + + // Dump pre-dungeon tiles. + char output_path[MAX_PATH]; + //sprintf(output_path, "testdata/pre-tiles_%d.bin", g.seed); + //write_file(output_path, pdungeon, sizeof(pdungeon)); + + // Dump tiles. + sprintf(output_path, "testdata/dlvl=%d,quest_id=%d,seed=%d,setlvl=%d_tiles.bin", g.dlvl, g.quest_id, g.seed, g.setlvl); + printf("creating '%s'\n", output_path); + write_file(output_path, dungeon, sizeof(dungeon)); + + // Dungeon dungeon pieces. + sprintf(output_path, "testdata/dlvl=%d,quest_id=%d,seed=%d,setlvl=%d_dpieces.bin", g.dlvl, g.quest_id, g.seed, g.setlvl); + printf("creating '%s'\n", output_path); + write_file(output_path, dPiece, sizeof(dPiece)); + + // Dungeon arches. + sprintf(output_path, "testdata/dlvl=%d,quest_id=%d,seed=%d,setlvl=%d_arches.bin", g.dlvl, g.quest_id, g.seed, g.setlvl); + printf("creating '%s'\n", output_path); + write_file(output_path, dSpecial, sizeof(dSpecial)); + + // Dungeon transparency. + sprintf(output_path, "testdata/dlvl=%d,quest_id=%d,seed=%d,setlvl=%d_transparency.bin", g.dlvl, g.quest_id, g.seed, g.setlvl); + printf("creating '%s'\n", output_path); + write_file(output_path, dTransVal, sizeof(dTransVal)); + } +} + +void testgen() { + printf("testgen\n"); + gen_drlg_l1_tests(); +} + +void write_file(char *path, void *buf, int size) { + FILE *f = fopen(path, "wb"); + if (f == NULL) { + fprintf(stderr, "unable to create file %s", path); + exit(1); + } + if (fwrite(buf, 1, size, f) != size) { + fprintf(stderr, "unable to write to file %s", path); + exit(1); + } + fclose(f); +} diff --git a/SourceT/testgen.h b/SourceT/testgen.h new file mode 100644 index 000000000..4d3caf77d --- /dev/null +++ b/SourceT/testgen.h @@ -0,0 +1,6 @@ +#ifndef __TESTGEN_H__ +#define __TESTGEN_H__ + +void testgen(); + +#endif // #ifndef __TESTGEN_H__ diff --git a/cmd/viewdun/main.go b/cmd/viewdun/main.go new file mode 100644 index 000000000..71eed095a --- /dev/null +++ b/cmd/viewdun/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "bytes" + "encoding/binary" + "flag" + "io/ioutil" + "log" + + "github.com/pkg/errors" +) + +func main() { + flag.Parse() + for _, tileIDBinPath := range flag.Args() { + tileIDMap, err := genDun(tileIDBinPath) + if err != nil { + log.Fatalf("%+v", err) + } + dunPath := tileIDBinPath + ".dun" + if err := dumpDun(dunPath, tileIDMap); err != nil { + log.Fatalf("%+v", err) + } + } +} + +func genDun(tileIDBinPath string) (*[40][40]uint8, error) { + buf, err := ioutil.ReadFile(tileIDBinPath) + if err != nil { + return nil, errors.WithStack(err) + } + r := bytes.NewReader(buf) + tileIDMap := new([40][40]byte) + if err := binary.Read(r, binary.LittleEndian, tileIDMap); err != nil { + return nil, errors.WithStack(err) + } + return tileIDMap, nil +} + +func dumpDun(dunPath string, tileIDMap *[40][40]uint8) error { + const ( + width = 40 + height = 40 + ) + buf := make([]uint16, 0) + buf = append(buf, width) + buf = append(buf, height) + for y := 0; y < 40; y++ { + for x := 0; x < 40; x++ { + buf = append(buf, uint16(tileIDMap[x][y])) + } + } + b := &bytes.Buffer{} + binary.Write(b, binary.LittleEndian, buf) + if err := ioutil.WriteFile(dunPath, b.Bytes(), 0644); err != nil { + return errors.WithStack(err) + } + return nil +} diff --git a/gen_tests.sh b/gen_tests.sh new file mode 100755 index 000000000..e2efc551a --- /dev/null +++ b/gen_tests.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +echo "Building testgen.exe" +make -f MakefileVC testgen + +echo "Writing test cases to testdata/" +mkdir -p testdata +wine testgen.exe diff --git a/types.h b/types.h index 6553268dc..b5a69d400 100644 --- a/types.h +++ b/types.h @@ -51,7 +51,7 @@ #endif // If defined, use copy protection [Default -> Defined] -#if !defined(_DEBUG) && !defined(SPAWN) +#if !defined(_DEBUG) && !defined(SPAWN) && !defined(TESTGEN) #define COPYPROT #endif