mirror of
https://github.com/morluto/rea.git
synced 2026-10-18 20:10:13 -05:00
fix(apktool): derive locale size from resource configuration fields
Allow the 23-character script-region-variant representation emitted by Apktool, retaining the shared admission grammar and request aliases. Exact discovery and CLI/MCP selection consume the same bound. Validation: 67 focused tests across four files; five controls fail on the baseline; static/docs checks and two source reviews. Twelve ordinary tiny CLI/MCP task comparisons preserve Evidence; two actual authored-launcher long-locale CLI/MCP calls select the table. No real Apktool/JVM or native Windows verification. Fixes #4817
This commit is contained in:
1 parent
98886adddf
commit
6385ef4355
6 files changed
+88
-15
No files matched your search
@@ -65,6 +65,9 @@ select Android's `values-pt-rBR`; Android qualifiers such as `pt-rBR` and
|
||||
default table is bounded to 2000 entries with partial coverage when more
|
||||
exist. `locales` lists observed regular `strings.xml` tables whose exact
|
||||
`values-<locale>` directory is selectable by the request's locale grammar.
|
||||
Locale suffixes allow up to 23 ASCII characters, derived from Apktool's
|
||||
language, script, region, and variant fields; for example,
|
||||
`b+sr+Latn+RS+REVISED` selects that exact table.
|
||||
Other table directories, including combined qualifiers such as
|
||||
`values-en-rUS-v21` and `values-mcc310-fr`, are named in limitations with
|
||||
their source spelling. They are not advertised as selectable locales;
|
||||
|
||||
@@ -928,16 +928,30 @@ it("discloses qualified and unfamiliar strings tables while preserving exact loc
|
||||
"night",
|
||||
"v21",
|
||||
"pt-BR",
|
||||
"b+sr+Latn+RS+REVISED",
|
||||
"unknown_CONFIGURATION",
|
||||
];
|
||||
const { command, root } = await writeApktoolStub(
|
||||
decodeStub({ extraValuesDirectories: [...tables, "fr", "b+sr+Latn"] }),
|
||||
decodeStub({
|
||||
extraValuesDirectories: [
|
||||
...tables,
|
||||
"fr",
|
||||
"b+sr+Latn",
|
||||
"b+sr+Latn+RS+REVISED",
|
||||
"b+fil+Latn+419+ABCDEFGH",
|
||||
],
|
||||
}),
|
||||
);
|
||||
const apk = await apkFixture(root);
|
||||
const provider = providerOver(command);
|
||||
try {
|
||||
for (const locale of [undefined, "fr", "b+sr+Latn", "en-rUS"] as const) {
|
||||
for (const locale of [
|
||||
undefined,
|
||||
"fr",
|
||||
"b+sr+Latn",
|
||||
"b+sr+Latn+RS+REVISED",
|
||||
"b+fil+Latn+419+ABCDEFGH",
|
||||
"en-rUS",
|
||||
] as const) {
|
||||
const result = await provider.execute({
|
||||
operation: "decode_android_resources",
|
||||
input: {
|
||||
@@ -948,7 +962,13 @@ it("discloses qualified and unfamiliar strings tables while preserving exact loc
|
||||
});
|
||||
if (!result.ok) throw result.error;
|
||||
expect(result.value.result).toMatchObject({
|
||||
locales: ["b+sr+Latn", "de", "fr"],
|
||||
locales: [
|
||||
"b+fil+Latn+419+ABCDEFGH",
|
||||
"b+sr+Latn",
|
||||
"b+sr+Latn+RS+REVISED",
|
||||
"de",
|
||||
"fr",
|
||||
],
|
||||
locale: locale ?? null,
|
||||
coverage: "complete",
|
||||
});
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import { expect, it } from "vitest";
|
||||
import {
|
||||
androidLocaleDirectorySuffix,
|
||||
androidLocaleQualifierSchema,
|
||||
classifyAndroidStringsTable,
|
||||
} from "./apktoolResourceAnalysis.js";
|
||||
|
||||
it.each(["de", "pt-rBR", "en-r419", "b+sr+Latn", "b+zh+Hans+CN"])(
|
||||
"recognizes the exact selectable strings table values-%s",
|
||||
(suffix) => {
|
||||
expect(classifyAndroidStringsTable(suffix)).toEqual({
|
||||
kind: "locale",
|
||||
locale: suffix,
|
||||
});
|
||||
},
|
||||
);
|
||||
it.each([
|
||||
"de",
|
||||
"pt-rBR",
|
||||
"en-r419",
|
||||
"b+sr+Latn",
|
||||
"b+zh+Hans+CN",
|
||||
"b+sr+Latn+RS+REVISED",
|
||||
"b+fil+Latn+419+ABCDEFGH",
|
||||
])("recognizes the exact selectable strings table values-%s", (suffix) => {
|
||||
expect(classifyAndroidStringsTable(suffix)).toEqual({
|
||||
kind: "locale",
|
||||
locale: suffix,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
"en-rUS-v21",
|
||||
@@ -25,7 +31,6 @@ it.each([
|
||||
"v21",
|
||||
"pt-BR",
|
||||
"en-419",
|
||||
"b+sr+Latn+RS+REVISED",
|
||||
"unknown_CONFIGURATION",
|
||||
"",
|
||||
])(
|
||||
@@ -49,3 +54,20 @@ it.each([
|
||||
expect(androidLocaleDirectorySuffix(locale)).toBe(suffix);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
"b+fil+Latn+419+ABCDEFGH+US",
|
||||
"b+sr+Latn+RS+REVISED-v21",
|
||||
"../values-fr",
|
||||
"car",
|
||||
"night",
|
||||
])(
|
||||
"refuses suffixes outside the bounded locale selection contract: %s",
|
||||
(suffix) => {
|
||||
expect(androidLocaleQualifierSchema.safeParse(suffix).success).toBe(false);
|
||||
expect(classifyAndroidStringsTable(suffix)).toEqual({
|
||||
kind: "other",
|
||||
suffix,
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -14,10 +14,15 @@ const ANDROID_NON_LOCALE_QUALIFIERS = new Set([
|
||||
"notnight",
|
||||
]);
|
||||
|
||||
// Apktool emits b+language[+script][+region][+variant] from ARSC fields:
|
||||
// language/region expand to at most 3 ASCII characters, script has 4 bytes,
|
||||
// and variant has 8 bytes (BinaryResourceParser / ResConfig).
|
||||
const MAX_ANDROID_LOCALE_SUFFIX = 2 + 3 + (1 + 4) + (1 + 3) + (1 + 8);
|
||||
|
||||
const localeQualifierSchema = z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(16)
|
||||
.max(MAX_ANDROID_LOCALE_SUFFIX)
|
||||
.regex(
|
||||
/^(?:[a-z]{2,3}(?:-(?:r?[A-Z]{2}|r?[0-9]{3}))?|b\+[a-z]{2,3}(?:\+[A-Za-z0-9]{2,8})*)$/u,
|
||||
"Locale must look like de, pt-BR, pt-rBR, or b+zh+Hans+CN",
|
||||
|
||||
@@ -56,6 +56,8 @@ const OWNER_ADMITTED_LOCALES = [
|
||||
"zh-rCN",
|
||||
"en-419",
|
||||
"b+zh+Hans+CN",
|
||||
"b+sr+Latn+RS+REVISED",
|
||||
"b+fil+Latn+419+ABCDEFGH",
|
||||
] as const;
|
||||
|
||||
it("forwards every locale the apktool input owner admits", async () => {
|
||||
|
||||
@@ -44,12 +44,15 @@ const results: Readonly<Record<string, unknown>> = {
|
||||
};
|
||||
|
||||
it("advertises exact valid Apktool schemas and records inline evidence through MCP", async () => {
|
||||
const requestedLocales: (string | undefined)[] = [];
|
||||
const provider: ApktoolResourceAnalysisPort = {
|
||||
async inspectAvailability() {
|
||||
return { status: "available", code: null, reason: null, diagnostics: {} };
|
||||
},
|
||||
async close() {},
|
||||
async execute(request) {
|
||||
if (request.operation === "decode_android_resources")
|
||||
requestedLocales.push(request.input.locale);
|
||||
return ok(createAnalysisExecution(results[request.operation], identity));
|
||||
},
|
||||
};
|
||||
@@ -77,6 +80,24 @@ it("advertises exact valid Apktool schemas and records inline evidence through M
|
||||
for (const name of operationNames) expect(names, name).toContain(name);
|
||||
|
||||
assertAdvertisedMcpContracts(operationNames, tools);
|
||||
const localeSchema = tools.find(
|
||||
(tool) => tool.name === "decode_android_resources",
|
||||
)?.inputSchema.properties?.locale;
|
||||
expect(localeSchema).toMatchObject({ maxLength: 23 });
|
||||
for (const locale of ["b+sr+Latn+RS+REVISED", "b+fil+Latn+419+ABCDEFGH"]) {
|
||||
const reply = await client.callTool({
|
||||
name: "decode_android_resources",
|
||||
arguments: { path: "/targets/probe.apk", locale },
|
||||
});
|
||||
expect(reply.isError).not.toBe(true);
|
||||
expect(parseEvidence(reply.structuredContent).parameters).toMatchObject({
|
||||
locale,
|
||||
});
|
||||
}
|
||||
expect(requestedLocales).toEqual([
|
||||
"b+sr+Latn+RS+REVISED",
|
||||
"b+fil+Latn+419+ABCDEFGH",
|
||||
]);
|
||||
|
||||
const decoded = await client.callTool({
|
||||
name: "decode_android_resources",
|
||||
|
||||
Reference in new issue
Block a user