1/**
2 * Copyright (c) 2006-2023 LOVE Development Team
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 **/
20
21#include <cassert>
22#include <algorithm>
23
24#include "PhysfsIo.h"
25
26namespace love
27{
28namespace filesystem
29{
30namespace physfs
31{
32
33bool StripSuffixIo::determineStrippedLength()
34{
35 if (!file) {
36 return false;
37 }
38 const int64_t fullSize = fullLength();
39 int64_t chunkSize = std::min(fullSize, (int64_t)8192);
40 std::string buffer;
41 buffer.reserve(chunkSize);
42 int64_t i = fullSize - chunkSize;
43 // I don't think we really need to go through the whole file. The main known use
44 // case for this functionality is to skip windows codesign signatures, which are
45 // from what I have seen ~12KB or so, but trying is better than just failing.
46 while (i >= 0)
47 {
48 buffer.resize(chunkSize);
49 if (seek(i) == 0)
50 return false;
51 const auto n = read(&buffer[0], chunkSize);
52 if (n <= 0)
53 return false;
54 buffer.resize(n);
55 // We are looking for the magic bytes that indicate the start
56 // of the "End of cental directory record (EOCD)".
57 // As this is most likely not a multi-disk zip, we could include 4 bytes of 0x00,
58 // but I'd rather make fewer assumptions.
59 const auto endOfCentralDirectory = buffer.rfind("\x50\x4B\x05\x06");
60 if (endOfCentralDirectory != std::string::npos)
61 {
62 i = i + endOfCentralDirectory;
63 break;
64 }
65 if (i == 0)
66 break;
67 i = std::max((int64_t)0, i - chunkSize);
68 }
69
70 if (i > 0)
71 {
72 // The EOCD record is at least 22 bytes but may include a comment
73 if (i + 22 > fullSize)
74 return false; // Incomplete central directory
75 // The comment length (u16) is located 20 bytes from the start of the EOCD record
76 if (seek(i + 20) == 0)
77 return false;
78 uint8_t buffer[2];
79 const auto n = read(buffer, 2);
80 if (n <= 0)
81 return false;
82 const auto commentSize = (buffer[1] << 8) | buffer[0];
83 if (i + 22 + commentSize > fullSize) // Comment incomplete
84 return false;
85 // We pretend the file ends just after the comment
86 // (which should be the end of the embedded zip file)
87 strippedLength_ = i + 22 + commentSize;
88 }
89 else
90 {
91 strippedLength_ = fullSize;
92 }
93
94 if (seek(0) == 0)
95 return false;
96 return true;
97}
98
99int64_t StripSuffixIo::read(void* buf, uint64_t len)
100{
101 if (!file)
102 {
103 PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
104 return -1;
105 }
106 const auto ret = std::fread(buf, 1, len, file);
107 if (ret == 0)
108 {
109 if (std::feof(file))
110 {
111 PHYSFS_setErrorCode(PHYSFS_ERR_OK);
112 return 0;
113 }
114 else
115 {
116 PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
117 return -1;
118 }
119 }
120 else if (ret < len && std::ferror(file))
121 {
122 PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
123 return -1;
124 }
125 PHYSFS_setErrorCode(PHYSFS_ERR_OK);
126 return ret;
127}
128
129int64_t StripSuffixIo::write(const void* /*buf*/, uint64_t /*len*/)
130{
131 PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
132 return -1;
133}
134
135int64_t StripSuffixIo::seek(uint64_t offset)
136{
137 if (!file)
138 {
139 PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
140 return 0;
141 }
142 const auto ret = std::fseek(file, offset, SEEK_SET);
143 PHYSFS_setErrorCode(ret != 0 ? PHYSFS_ERR_OS_ERROR : PHYSFS_ERR_OK);
144 return ret == 0 ? 1 : 0;
145}
146
147int64_t StripSuffixIo::tell()
148{
149 if (!file)
150 {
151 PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
152 return -1;
153 }
154 return std::ftell(file);
155}
156
157int64_t StripSuffixIo::length()
158{
159 return strippedLength_;
160}
161
162int64_t StripSuffixIo::flush()
163{
164 if (!file)
165 {
166 PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
167 return 0;
168 }
169 return std::fflush(file) == 0 ? 1 : 0;
170}
171
172int64_t StripSuffixIo::fullLength()
173{
174 assert(file);
175 const auto cur = std::ftell(file);
176 if (cur == -1)
177 {
178 PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
179 return -1;
180 }
181 if (std::fseek(file, 0, SEEK_END) != 0)
182 {
183 PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
184 return -1;
185 }
186 const auto len = std::ftell(file);
187 if (len == -1)
188 {
189 PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
190 return -1;
191 }
192 if (std::fseek(file, cur, SEEK_SET) != 0)
193 {
194 // We do have the length now, but something is wrong, so we return an error anyways
195 PHYSFS_setErrorCode(PHYSFS_ERR_OS_ERROR);
196 return -1;
197 }
198 return len;
199}
200
201} // physfs
202} // filesystem
203} // love
204