]> git.localhorst.tv Git - blank.git/blob - src/io/WorldSave.cpp
a08d69c3c288784ac312b84c32cdb461c209baa4
[blank.git] / src / io / WorldSave.cpp
1 #include "WorldSave.hpp"
2
3 #include "filesystem.hpp"
4
5 #include <cctype>
6 #include <fstream>
7 #include <iostream>
8 #include <limits>
9 #include <stdexcept>
10 #include <zlib.h>
11
12 using namespace std;
13
14
15 namespace blank {
16
17 WorldSave::WorldSave(const string &path)
18 : root_path(path)
19 , conf_path(path + "world.conf")
20 , chunk_path(path + "chunks/%d/%d/%d.gz")
21 , chunk_bufsiz(chunk_path.length() + 3 * std::numeric_limits<int>::digits10)
22 , chunk_buf(new char[chunk_bufsiz]) {
23
24 }
25
26
27 bool WorldSave::Exists() const noexcept {
28         return is_dir(root_path) && is_file(conf_path);
29 }
30
31
32 void WorldSave::Read(World::Config &conf) const {
33         ifstream in(conf_path);
34         if (!in) {
35                 throw runtime_error("failed to open world config");
36         }
37
38         constexpr char spaces[] = "\n\r\t ";
39
40         string line;
41         while (getline(in, line)) {
42                 if (line.empty() || line[0] == '#') continue;
43                 auto equals_pos = line.find_first_of('=');
44
45                 auto name_begin = line.find_first_not_of(spaces, 0, sizeof(spaces));
46                 auto name_end = equals_pos - 1;
47                 while (name_end > name_begin && isspace(line[name_end])) {
48                         --name_end;
49                 }
50
51                 auto value_begin = line.find_first_not_of(spaces, equals_pos + 1, sizeof(spaces));
52                 auto value_end = line.length() - 1;
53                 while (value_end > value_begin && isspace(line[value_end])) {
54                         --value_end;
55                 }
56
57                 string name(line, name_begin, name_end - name_begin + 1);
58                 string value(line, value_begin, value_end - value_begin + 1);
59
60                 if (name == "seed") {
61                         conf.gen.seed = stoul(value);
62                 } else {
63                         throw runtime_error("unknown world option: " + name);
64                 }
65         }
66         if (in.bad()) {
67                 throw runtime_error("IO error reading world config");
68         }
69 }
70
71 void WorldSave::Write(const World::Config &conf) const {
72         if (!make_dirs(root_path)) {
73                 throw runtime_error("failed to create world save directory");
74         }
75
76         ofstream out(conf_path);
77         out << "seed = " << conf.gen.seed << endl;
78         out.close();
79
80         if (!out) {
81                 throw runtime_error("failed to write world config");
82         }
83 }
84
85
86 bool WorldSave::Exists(const Chunk::Pos &pos) const noexcept {
87         return is_file(ChunkPath(pos));
88 }
89
90 void WorldSave::Read(Chunk &chunk) const {
91         const char *path = ChunkPath(chunk.Position());
92         gzFile file = gzopen(path, "r");
93         if (!file) {
94                 throw runtime_error("failed to open chunk file");
95         }
96         if (gzread(file, chunk.BlockData(), Chunk::BlockSize()) != Chunk::BlockSize()) {
97                 throw runtime_error("failed to read chunk from file");
98         }
99         if (gzclose(file) != Z_OK) {
100                 throw runtime_error("failed to read chunk file");
101         }
102         chunk.InvalidateModel();
103         chunk.ClearSave();
104 }
105
106 void WorldSave::Write(Chunk &chunk) const {
107         const char *path = ChunkPath(chunk.Position());
108         gzFile file = gzopen(path, "w");
109         if (!file) {
110                 // check if it's because of a missing path component
111                 if (errno != ENOENT) {
112                         // nope, fatal
113                         throw runtime_error(strerror(errno));
114                 }
115                 string dir_path(path);
116                 dir_path.erase(dir_path.find_last_of("\\/"));
117                 if (!make_dirs(dir_path)) {
118                         throw runtime_error("failed to create dir for chunk file");
119                 }
120                 file = gzopen(path, "w");
121                 if (!file) {
122                         throw runtime_error("failed to open chunk file");
123                 }
124         }
125         if (gzwrite(file, chunk.BlockData(), Chunk::BlockSize()) == 0) {
126                 gzclose(file); // if this fails, it can't be helped
127                 throw runtime_error("failed to write chunk to file");
128         }
129         if (gzclose(file) != Z_OK) {
130                 throw runtime_error("failed to write chunk file");
131         }
132         chunk.ClearSave();
133 }
134
135
136 const char *WorldSave::ChunkPath(const Chunk::Pos &pos) const {
137         snprintf(chunk_buf.get(), chunk_bufsiz, chunk_path.c_str(), pos.x, pos.y, pos.z);
138         return chunk_buf.get();
139 }
140
141 }