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
|
/* Homepage http://facetracknoir.sourceforge.net/home/default.htm
*
* ISC License (ISC) *
* *
* Copyright (c) 2015, Wim Vriend *
* *
* Permission to use, copy, modify, and/or distribute this software for any *
* purpose with or without fee is hereby granted, provided that the above *
* copyright notice and this permission notice appear in all copies. *
*/
#include "csv.h"
#include "compat/library-path.hpp"
#include <utility>
#include <cstdio>
#include <QByteArray>
#include <QString>
#include <QStringDecoder>
#include <QFile>
#include <QByteArrayView>
#include <QDebug>
namespace {
void chomp(QString& str)
{
if (!str.isEmpty() && str.back() == '\n')
{
str.chop(1);
if (!str.isEmpty() && str.back() == '\r')
str.chop(1);
}
}
auto do_scanf(QLatin1StringView s, unsigned(&tmp)[8])
{
unsigned fuzz[3];
return std::sscanf(s.constData(),
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
fuzz + 2,
fuzz + 0,
tmp + 3, tmp + 2, tmp + 1, tmp + 0,
tmp + 7, tmp + 6, tmp + 5, tmp + 4,
fuzz + 1);
};
}
bool getGameData(int id, unsigned char* table, QString& gamename)
{
for (int i = 0; i < 8; i++)
table[i] = 0;
if (id != 0)
qDebug() << "csv: lookup game id" << id;
static const QString csv_path(OPENTRACK_BASE_PATH +
OPENTRACK_DOC_PATH "settings/facetracknoir supported games.csv");
auto id_str = QString::number(id);
QFile file(csv_path);
if (!file.open(QIODevice::ReadOnly))
{
qDebug() << "csv: can't open game list for freetrack protocol!";
return false;
}
QStringDecoder decoder{QStringConverter::Encoding::Utf8};
QStringList gameLine; gameLine.reserve(8);
unsigned lineno = 0;
// TODO QIODevice::readLineInto() is Qt 6.9 - sh 20250515
char buf[256];
while (auto sz = file.readLine(buf, sizeof(buf)))
{
QString line = decoder.decode(QByteArrayView{buf, sz});
chomp(line);
if (line.isEmpty())
continue;
gameLine = line.split(';', Qt::SplitBehaviorFlags::KeepEmptyParts);
//qDebug() << "Column 0: " << gameLine.at(0); // No.
//qDebug() << "Column 1: " << gameLine.at(1); // Game Name
//qDebug() << "Column 2: " << gameLine.at(2); // Game Protocol
//qDebug() << "Column 3: " << gameLine.at(3); // Supported since version
//qDebug() << "Column 4: " << gameLine.at(4); // Verified
//qDebug() << "Column 5: " << gameLine.at(5); // By
//qDebug() << "Column 6: " << gameLine.at(6); // International ID
//qDebug() << "Column 7: " << gameLine.at(7); // FaceTrackNoIR ID
if (gameLine.size() == 8)
{
if (gameLine[6] == id_str)
{
const QString& proto = gameLine[3];
QString& name = gameLine[1];
const QByteArray id_cstr = gameLine[7].toLatin1();
unsigned tmp[8] {};
if (proto == QStringLiteral("V160") || id_cstr.length() != 22)
(void)0;
else if (id_cstr.length() != 22 || do_scanf(QLatin1StringView(id_cstr), tmp) != 11)
qDebug() << "scanf failed" << lineno;
else
{
using uchar = unsigned char;
for (int i = 0; i < 8; i++)
table[i] = uchar(tmp[i]);
}
gamename = std::move(name);
return true;
}
}
else
qDebug() << "csv wrong column count" << gameLine.size();
lineno++;
}
if (id)
qDebug() << "unknown game connected" << id;
return false;
}
|