aboutsummaryrefslogtreecommitdiffstats
path: root/Alc/compat.h
blob: dc652bcad18c6a31ddb294b49c4e2c29dbdf308b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#ifndef AL_COMPAT_H
#define AL_COMPAT_H

#ifdef __cplusplus

#ifdef _WIN32

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include <array>
#include <string>
#include <fstream>

inline std::string wstr_to_utf8(const WCHAR *wstr)
{
    std::string ret;

    int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
    if(len > 0)
    {
        ret.resize(len);
        WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &ret[0], len, nullptr, nullptr);
        ret.pop_back();
    }

    return ret;
}

inline std::wstring utf8_to_wstr(const char *str)
{
    std::wstring ret;

    int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
    if(len > 0)
    {
        ret.resize(len);
        MultiByteToWideChar(CP_UTF8, 0, str, -1, &ret[0], len);
        ret.pop_back();
    }

    return ret;
}


namespace al {

// Windows' std::ifstream fails with non-ANSI paths since the standard only
// specifies names using const char* (or std::string). MSVC has a non-standard
// extension using const wchar_t* (or std::wstring?) to handle Unicode paths,
// but not all Windows compilers support it. So we have to make our own istream
// that accepts UTF-8 paths and forwards to Unicode-aware I/O functions.
class filebuf final : public std::streambuf {
    std::array<char_type,4096> mBuffer;
    HANDLE mFile{INVALID_HANDLE_VALUE};

    int_type underflow() override
    {
        if(mFile != INVALID_HANDLE_VALUE && gptr() == egptr())
        {
            // Read in the next chunk of data, and set the pointers on success
            DWORD got = 0;
            if(ReadFile(mFile, mBuffer.data(), (DWORD)mBuffer.size(), &got, nullptr))
                setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got);
        }
        if(gptr() == egptr())
            return traits_type::eof();
        return traits_type::to_int_type(*gptr());
    }

    pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override
    {
        if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
            return traits_type::eof();

        LARGE_INTEGER fpos;
        switch(whence)
        {
            case std::ios_base::beg:
                fpos.QuadPart = offset;
                if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN))
                    return traits_type::eof();
                break;

            case std::ios_base::cur:
                // If the offset remains in the current buffer range, just
                // update the pointer.
                if((offset >= 0 && offset < off_type(egptr()-gptr())) ||
                   (offset < 0 && -offset <= off_type(gptr()-eback())))
                {
                    // Get the current file offset to report the correct read
                    // offset.
                    fpos.QuadPart = 0;
                    if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT))
                        return traits_type::eof();
                    setg(eback(), gptr()+offset, egptr());
                    return fpos.QuadPart - off_type(egptr()-gptr());
                }
                // Need to offset for the file offset being at egptr() while
                // the requested offset is relative to gptr().
                offset -= off_type(egptr()-gptr());
                fpos.QuadPart = offset;
                if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT))
                    return traits_type::eof();
                break;

            case std::ios_base::end:
                fpos.QuadPart = offset;
                if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_END))
                    return traits_type::eof();
                break;

            default:
                return traits_type::eof();
        }
        setg(nullptr, nullptr, nullptr);
        return fpos.QuadPart;
    }

    pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override
    {
        // Simplified version of seekoff
        if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
            return traits_type::eof();

        LARGE_INTEGER fpos;
        fpos.QuadPart = pos;
        if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN))
            return traits_type::eof();

        setg(nullptr, nullptr, nullptr);
        return fpos.QuadPart;
    }

public:
    bool open(const wchar_t *filename, std::ios_base::openmode mode)
    {
        if((mode&std::ios_base::out) || !(mode&std::ios_base::in))
            return false;
        HANDLE f{CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, nullptr,
                             OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
        if(f == INVALID_HANDLE_VALUE) return false;

        if(mFile != INVALID_HANDLE_VALUE)
            CloseHandle(mFile);
        mFile = f;

        setg(nullptr, nullptr, nullptr);
        return true;
    }
    bool open(const char *filename, std::ios_base::openmode mode)
    {
        std::wstring wname{utf8_to_wstr(filename)};
        return open(wname.c_str(), mode);
    }

    bool is_open() const noexcept { return mFile != INVALID_HANDLE_VALUE; }

    filebuf() = default;
    ~filebuf() override
    {
        if(mFile != INVALID_HANDLE_VALUE)
            CloseHandle(mFile);
        mFile = INVALID_HANDLE_VALUE;
    }
};

// Inherit from std::istream to use our custom streambuf
class ifstream final : public std::istream {
    filebuf mStreamBuf;

public:
    ifstream(const std::wstring &filename, std::ios_base::openmode mode = std::ios_base::in)
      : ifstream(filename.c_str(), mode) { }
    ifstream(const wchar_t *filename, std::ios_base::openmode mode = std::ios_base::in)
      : std::istream{nullptr}
    {
        init(&mStreamBuf);

        // Set the failbit if the file failed to open.
        if((mode&std::ios_base::out) ||
           !mStreamBuf.open(filename, mode|std::ios_base::in))
            clear(failbit);
    }

    ifstream(const std::string &filename, std::ios_base::openmode mode = std::ios_base::in)
      : ifstream(filename.c_str(), mode) { }
    ifstream(const char *filename, std::ios_base::openmode mode = std::ios_base::in)
      : std::istream{nullptr}
    {
        init(&mStreamBuf);

        // Set the failbit if the file failed to open.
        if((mode&std::ios_base::out) ||
           !mStreamBuf.open(filename, mode|std::ios_base::in))
            clear(failbit);
    }

    bool is_open() const noexcept { return mStreamBuf.is_open(); }
};

} // namespace al

#define HAVE_DYNLOAD 1

#else /* _WIN32 */

#include <fstream>

namespace al {

using filebuf = std::filebuf;
using ifstream = std::ifstream;

} // namespace al

#if defined(HAVE_DLFCN_H)
#define HAVE_DYNLOAD 1
#endif

#endif /* _WIN32 */

#include <string>

struct PathNamePair { std::string path, fname; };
const PathNamePair &GetProcBinary(void);

#ifdef HAVE_DYNLOAD
void *LoadLib(const char *name);
void CloseLib(void *handle);
void *GetSymbol(void *handle, const char *name);
#endif

#endif /* __cplusplus */

#endif /* AL_COMPAT_H */