Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,13 @@ https://raw.githubusercontent.com/Yu9191/wloc/refs/heads/main/modules/wloc.modul
| longitude | 目标经度(在线选点优先) | null (透传) |
| latitude | 目标纬度(在线选点优先) | null (透传) |
| accuracy | 精度(米) | 25 |
| randomRadius | 每次响应在目标点周围随机扰动的最大半径(米),0 为关闭 | 30 |
| logLevel | 日志级别 | info |

优先级: 在线选点储存 > 模块参数 > 默认值

随机扰动采用圆形范围均匀采样。每次 WLOC 响应会生成一个新坐标,同一响应内的所有 WiFi/基站记录使用同一坐标,避免单次定位数据互相冲突。

</details>

<details>
Expand Down
4 changes: 2 additions & 2 deletions dist/wloc.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion modules/wloc.lpx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
longitude = input, "113.94114", tag=经度(在线选点优先)
latitude = input, "22.544577", tag=纬度(在线选点优先)
accuracy = input, "25", tag=精度(米)
randomRadius = input, "30", tag=扰动半径(米,0 为关闭)
logLevel = select, "info", "off", "error", "warn", "debug", "all", tag=日志级别

[Script]
http-response ^https?:\/\/gs-loc(-cn)?\.apple\.com\/clls\/wloc script-path=https://raw.githubusercontent.com/Yu9191/wloc/refs/heads/main/dist/wloc.js, requires-body=true, binary-body-mode=true, timeout=30, tag=Apple WLOC, argument=[{longitude},{latitude},{accuracy},{logLevel}]
http-response ^https?:\/\/gs-loc(-cn)?\.apple\.com\/clls\/wloc script-path=https://raw.githubusercontent.com/Yu9191/wloc/refs/heads/main/dist/wloc.js, requires-body=true, binary-body-mode=true, timeout=30, tag=Apple WLOC, argument=[{longitude},{latitude},{accuracy},{randomRadius},{logLevel}]
http-request ^https?:\/\/gs-loc(-cn)?\.apple\.com\/wloc-settings\/save script-path=https://raw.githubusercontent.com/Yu9191/wloc/refs/heads/main/dist/wloc-settings.js, timeout=10, tag=WLOC Settings

[MITM]
Expand Down
2 changes: 1 addition & 1 deletion modules/wloc.module
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#!category=Tools

[Script]
Apple WLOC = type=http-response,pattern=^https?:\/\/gs-loc(-cn)?\.apple\.com\/clls\/wloc,requires-body=1,binary-body-mode=1,max-size=0,timeout=30,script-path=https://raw.githubusercontent.com/Yu9191/wloc/refs/heads/main/dist/wloc.js,argument=longitude=113.94114&latitude=22.544577&accuracy=25&logLevel=info
Apple WLOC = type=http-response,pattern=^https?:\/\/gs-loc(-cn)?\.apple\.com\/clls\/wloc,requires-body=1,binary-body-mode=1,max-size=0,timeout=30,script-path=https://raw.githubusercontent.com/Yu9191/wloc/refs/heads/main/dist/wloc.js,argument=longitude=113.94114&latitude=22.544577&accuracy=25&randomRadius=30&logLevel=info
WLOC Settings = type=http-request,pattern=^https?:\/\/gs-loc(-cn)?\.apple\.com\/wloc-settings\/save,requires-body=0,max-size=0,timeout=10,script-path=https://raw.githubusercontent.com/Yu9191/wloc/refs/heads/main/dist/wloc-settings.js

[MITM]
Expand Down
6 changes: 3 additions & 3 deletions modules/wloc.sgmodule
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
#!author=Yu9191 Rewrite
#!homepage=https://github.com/Yu9191/wloc
#!category=Tools
#!arguments=经度:113.94114, 纬度:22.544577, 精度:25, 日志级别:info
#!arguments-desc=经度/纬度: 默认坐标(在线选点储存后优先)\n精度: GPS精度(米)\n日志级别: off/error/warn/info/debug/all\n\n使用方法: 打开选点页面 -> 选位置 -> 储存到设备
#!arguments=经度:113.94114, 纬度:22.544577, 精度:25, 扰动半径:30, 日志级别:info
#!arguments-desc=经度/纬度: 默认坐标(在线选点储存后优先)\n精度: GPS精度(米)\n扰动半径: 每次响应在目标点周围随机偏移的最大距离(米),0 为关闭\n日志级别: off/error/warn/info/debug/all\n\n使用方法: 打开选点页面 -> 选位置 -> 储存到设备

[Script]
Apple WLOC = type=http-response, pattern="^https?:\/\/gs-loc(-cn)?\.apple\.com\/clls\/wloc", requires-body=1, binary-body-mode=1, max-size=0, timeout=30, script-path=https://raw.githubusercontent.com/Yu9191/wloc/refs/heads/main/dist/wloc.js, argument=longitude={{{经度}}}&latitude={{{纬度}}}&accuracy={{{精度}}}&logLevel={{{日志级别}}}
Apple WLOC = type=http-response, pattern="^https?:\/\/gs-loc(-cn)?\.apple\.com\/clls\/wloc", requires-body=1, binary-body-mode=1, max-size=0, timeout=30, script-path=https://raw.githubusercontent.com/Yu9191/wloc/refs/heads/main/dist/wloc.js, argument=longitude={{{经度}}}&latitude={{{纬度}}}&accuracy={{{精度}}}&randomRadius={{{扰动半径}}}&logLevel={{{日志级别}}}
WLOC Settings = type=http-request, pattern="^https?:\/\/gs-loc(-cn)?\.apple\.com\/wloc-settings\/save", requires-body=0, max-size=0, timeout=10, script-path=https://raw.githubusercontent.com/Yu9191/wloc/refs/heads/main/dist/wloc-settings.js

[MITM]
Expand Down
2 changes: 1 addition & 1 deletion modules/wloc.stoverride
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ http:
binary-mode: true
max-size: 0
timeout: 30
argument: longitude=113.94114&latitude=22.544577&accuracy=25&logLevel=info
argument: longitude=113.94114&latitude=22.544577&accuracy=25&randomRadius=30&logLevel=info
- match: ^https?:\/\/gs-loc(-cn)?\.apple\.com\/wloc-settings\/save
name: WLOC.Settings
type: request
Expand Down
190 changes: 190 additions & 0 deletions tests/wloc-random.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const test = require("node:test");
const vm = require("node:vm");

const script = fs.readFileSync(
path.join(__dirname, "..", "dist", "wloc.js"),
"utf8",
);

function varint(value) {
const bytes = [];
let remaining = value;
while (remaining >= 128) {
bytes.push((remaining % 128) | 128);
remaining = Math.floor(remaining / 128);
}
bytes.push(remaining);
return bytes;
}

function field(number, wireType, value) {
const tag = varint(number * 8 + wireType);
return wireType === 0
? [...tag, ...varint(value)]
: [...tag, ...varint(value.length), ...value];
}

function locationRecord() {
return [
...field(1, 0, 1),
...field(2, 0, 1),
...field(3, 0, 5),
];
}

function wifiRecord(mac) {
return [
...field(1, 2, Array.from(Buffer.from(mac))),
...field(2, 2, locationRecord()),
];
}

function responseFixture() {
const payload = [
...field(2, 2, wifiRecord("00:11:22:33:44:55")),
...field(2, 2, wifiRecord("66:77:88:99:aa:bb")),
];
return [...new Array(8).fill(0), payload.length >> 8, payload.length & 255, ...payload];
}

function readVarint(bytes, start) {
let value = 0;
let factor = 1;
let offset = start;
while (offset < bytes.length) {
const byte = bytes[offset++];
value += (byte & 127) * factor;
if (!(byte & 128)) return [value, offset];
factor *= 128;
}
throw new Error("truncated varint");
}

function readFields(bytes) {
const fields = [];
let offset = 0;
while (offset < bytes.length) {
const [tag, valueOffset] = readVarint(bytes, offset);
offset = valueOffset;
const number = Math.floor(tag / 8);
const wireType = tag & 7;
if (wireType === 0) {
const [value, nextOffset] = readVarint(bytes, offset);
fields.push({ number, wireType, value });
offset = nextOffset;
} else if (wireType === 2) {
const [length, dataOffset] = readVarint(bytes, offset);
fields.push({
number,
wireType,
value: bytes.slice(dataOffset, dataOffset + length),
});
offset = dataOffset + length;
} else {
throw new Error(`unsupported wire type ${wireType}`);
}
}
return fields;
}

function extractLocations(response) {
const bytes = Array.from((response.response || response).bodyBytes);
const length = (bytes[8] << 8) | bytes[9];
const payload = bytes.slice(10, 10 + length);
return readFields(payload)
.filter((item) => item.number === 2 && item.wireType === 2)
.map((item) => {
const wifi = readFields(item.value);
const location = readFields(
wifi.find((field) => field.number === 2 && field.wireType === 2).value,
);
return {
latitude:
location.find((field) => field.number === 1 && field.wireType === 0)
.value / 1e8,
longitude:
location.find((field) => field.number === 2 && field.wireType === 0)
.value / 1e8,
};
});
}

function distanceMeters(a, b) {
const toRadians = (degrees) => (degrees * Math.PI) / 180;
const lat1 = toRadians(a.latitude);
const lat2 = toRadians(b.latitude);
const deltaLat = lat2 - lat1;
const deltaLon = toRadians(b.longitude - a.longitude);
const h =
Math.sin(deltaLat / 2) ** 2 +
Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLon / 2) ** 2;
return 2 * 6378137 * Math.asin(Math.sqrt(h));
}

async function runWloc(randomRadius, randomValues, storedSettings = null) {
let randomIndex = 0;
const contextMath = Object.create(Math);
contextMath.random = () => randomValues[randomIndex++ % randomValues.length];
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error("wloc script timed out")), 500);
const context = {
Egern: {},
Math: contextMath,
console: { log() {} },
$argument: `longitude=121.4737&latitude=31.2304&accuracy=25&randomRadius=${randomRadius}&logLevel=off`,
$persistentStore: {
read: (key) =>
key === "wloc_settings" && storedSettings
? JSON.stringify(storedSettings)
: null,
write: () => true,
},
$request: { url: "https://gs-loc.apple.com/clls/wloc" },
$response: { bodyBytes: responseFixture(), headers: {} },
$done(result) {
clearTimeout(timeout);
resolve(result);
},
};
try {
vm.runInNewContext(script, context);
} catch (error) {
clearTimeout(timeout);
reject(error);
}
});
}

test("randomizes once per response and stays inside the configured radius", async () => {
const center = { longitude: 121.4737, latitude: 31.2304 };
const first = extractLocations(await runWloc(30, [0.25, 0]));
const second = extractLocations(await runWloc(30, [1, 0.25]));

assert.equal(first.length, 2);
assert.deepEqual(first[0], first[1]);
assert.deepEqual(second[0], second[1]);
assert.notDeepEqual(first[0], second[0]);
assert.ok(Math.abs(distanceMeters(center, first[0]) - 15) < 0.01);
assert.ok(Math.abs(distanceMeters(center, second[0]) - 30) < 0.01);
});

test("a zero radius disables coordinate perturbation", async () => {
const locations = extractLocations(await runWloc(0, [1, 0.5]));
assert.deepEqual(locations, [
{ longitude: 121.4737, latitude: 31.2304 },
{ longitude: 121.4737, latitude: 31.2304 },
]);
});

test("also perturbs coordinates loaded from persistent settings", async () => {
const center = { longitude: 116.397, latitude: 39.908 };
const locations = extractLocations(
await runWloc(30, [0.25, 0], { ...center, accuracy: 25 }),
);

assert.ok(Math.abs(distanceMeters(center, locations[0]) - 15) < 0.01);
assert.deepEqual(locations[0], locations[1]);
});