From ad1e3006745d3a657f22d990e45d8629dc0af5c9 Mon Sep 17 00:00:00 2001 From: Tom Arrow Date: Tue, 17 Jun 2025 09:06:24 +0000 Subject: [PATCH] Quake 3 Engine demo pattern & test file (#402) * Quake 3 Engine demo pattern & test file * Quake 3 Engine demo: Increase limits (demo files can be big) and additional condition * Quake 3 demo specify little endian * Quake 3 demo format: Add message type detection * Quake 3 demo format: Read serverTime for snapshot messages * Quake 3 demo pattern: fixed bug/typo/sleepy coding * Quake 3 demo pattern: Ability to read CS_SERVERINFO * Quake 3 demo pattern: Read CS_SYSTEMINFO as well * Quake 3 demo pattern: Read first serverCommand per message * Added Quake 3 engine demo pattern to readme * Change Quake 3 engine demo entry in readme table --------- Co-authored-by: Tom --- README.md | 1 + patterns/q3demo.hexpat | 196 +++++++++++++++++++ tests/patterns/test_data/q3demo.hexpat.dm_16 | Bin 0 -> 19492 bytes 3 files changed, 197 insertions(+) create mode 100644 patterns/q3demo.hexpat create mode 100644 tests/patterns/test_data/q3demo.hexpat.dm_16 diff --git a/README.md b/README.md index 12aea79..68934d7 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | PYC | `application/x-bytecode.python` | [`patterns/pyc.hexpat`](patterns/pyc.hexpat) | Python bytecode files | | QBCL | | [`patterns/qbcl.hexpat`](patterns/qbcl.hexpat) | Qubicle voxel scene project file | | QOI | `image/qoi` | [`patterns/qoi.hexpat`](patterns/qoi.hexpat) | QOI image files | +| Quake 3 engine demo | | [`patterns/q3demo.hexpat`](patterns/q3demo.hexpat) | Demos/replays of most Quake 3 engine games | | quantized-mesh | | [`patterns/quantized-mesh.hexpat`](patterns/quantized-mesh.hexpat) | Cesium quantized-mesh terrain | | RAR | `application/x-rar` | [`patterns/rar.hexpat`](patterns/rar.hexpat) | RAR archive file format | | RAS | `image/x-sun-raster` | [`patterns/ras.hexpat`](patterns/ras.hexpat) | RAS image files | diff --git a/patterns/q3demo.hexpat b/patterns/q3demo.hexpat new file mode 100644 index 0000000..4d013f2 --- /dev/null +++ b/patterns/q3demo.hexpat @@ -0,0 +1,196 @@ +#pragma description Quake 3 Engine demo + +#pragma array_limit 2147483647 +#pragma pattern_limit 2147483647 +#pragma loop_limit 16384 + +#define FINAL_DEMO_MESSAGE_NUMBER -1 +#define FINAL_DEMO_MESSAGE_LENGTH -1 +#define GETINT ((data[bitindex >> 3] | (data[(bitindex >> 3)+1]<<8) | (data[(bitindex >> 3)+2]<<16) | (data[(bitindex >> 3)+3]<<32)) >> (bitindex & 7)) & 0x7FF + +import type.magic; +import std.string; + +u16 huffdecode[2048]={2512,2182,512,2763,1859,2808,512,2360,1918,1988,512,1803,2158,2358,512,2180,1798,2053,512,1804,2603,1288,512,2166,2285,2167,512,1281,1640,2767,512,1664,1731,2116,512,2788,1791,1808,512,1840,2153,1921,512,2708,2723,1549,512,2046,1893,2717,512,2602,1801,1288,512,1568,2480,2062,512,1281,2145,2711,512,1543,1909,2150,512,2077,2338,2762,512,2162,1794,2024,512,2168,1922,2447,512,2334,1857,2117,512,2100,2240,1288,512,2186,2321,1908,512,1281,1640,2242,512,1664,1731,2729,512,2633,1791,1919,512,2184,1917,1802,512,2710,1795,1549,512,2172,2375,2789,512,2171,2187,1288,512,1568,2095,2163,512,1281,1858,1923,512,1543,2374,2446,512,2181,1859,2160,512,2183,1918,1988,512,1803,2161,2751,512,2413,1798,2529,512,1804,2344,1288,512,2404,2156,2786,512,1281,1640,2641,512,1664,1731,2052,512,2170,1791,1808,512,1840,2395,1921,512,2586,2319,1549,512,2046,1893,2101,512,2159,1801,1288,512,1568,2247,2773,512,1281,2365,2410,512,1543,1909,2781,512,2097,2411,2740,512,2396,1794,2024,512,2734,1922,2733,512,2112,1857,2528,512,2593,2079,1288,512,2648,2143,1908,512,1281,1640,2770,512,1664,1731,2169,512,2714,1791,1919,512,2185,1917,1802,512,2398,1795,1549,512,2098,2801,2361,512,2400,2328,1288,512,1568,2783,2713,512,1281,1858,1923,512,1543,2816,2182,512,2497,1859,2397,512,2794,1918,1988,512,1803,2158,2772,512,2180,1798,2053,512,1804,2464,1288,512,2166,2285,2167,512,1281,1640,2764,512,1664,1731,2116,512,2620,1791,1808,512,1840,2153,1921,512,2716,2384,1549,512,2046,1893,2448,512,2722,1801,1288,512,1568,2472,2062,512,1281,2145,2376,512,1543,1909,2150,512,2077,2366,2709,512,2162,1794,2024,512,2168,1922,2735,512,2407,1857,2117,512,2100,2240,1288,512,2186,2779,1908,512,1281,1640,2242,512,1664,1731,2359,512,2705,1791,1919,512,2184,1917,1802,512,2642,1795,1549,512,2172,2394,2645,512,2171,2187,1288,512,1568,2095,2163,512,1281,1858,1923,512,1543,2450,2771,512,2181,1859,2160,512,2183,1918,1988,512,1803,2161,2585,512,2403,1798,2619,512,1804,2777,1288,512,2355,2156,2362,512,1281,1640,2380,512,1664,1731,2052,512,2170,1791,1808,512,1840,2811,1921,512,2402,2601,1549,512,2046,1893,2101,512,2159,1801,1288,512,1568,2247,2719,512,1281,2747,2776,512,1543,1909,2725,512,2097,2445,2765,512,2638,1794,2024,512,2444,1922,2774,512,2112,1857,2727,512,2644,2079,1288,512,2800,2143,1908,512,1281,1640,2580,512,1664,1731,2169,512,2646,1791,1919,512,2185,1917,1802,512,2588,1795,1549,512,2098,2322,2504,512,2623,2350,1288,512,1568,2323,2721,512,1281,1858,1923,512,1543,2512,2182,512,2746,1859,2798,512,2360,1918,1988,512,1803,2158,2358,512,2180,1798,2053,512,1804,2745,1288,512,2166,2285,2167,512,1281,1640,2806,512,1664,1731,2116,512,2796,1791,1808,512,1840,2153,1921,512,2582,2761,1549,512,2046,1893,2793,512,2647,1801,1288,512,1568,2480,2062,512,1281,2145,2738,512,1543,1909,2150,512,2077,2338,2715,512,2162,1794,2024,512,2168,1922,2447,512,2334,1857,2117,512,2100,2240,1288,512,2186,2321,1908,512,1281,1640,2242,512,1664,1731,2795,512,2750,1791,1919,512,2184,1917,1802,512,2732,1795,1549,512,2172,2375,2604,512,2171,2187,1288,512,1568,2095,2163,512,1281,1858,1923,512,1543,2374,2446,512,2181,1859,2160,512,2183,1918,1988,512,1803,2161,2813,512,2413,1798,2529,512,1804,2344,1288,512,2404,2156,2743,512,1281,1640,2748,512,1664,1731,2052,512,2170,1791,1808,512,1840,2395,1921,512,2637,2319,1549,512,2046,1893,2101,512,2159,1801,1288,512,1568,2247,2812,512,1281,2365,2410,512,1543,1909,2799,512,2097,2411,2802,512,2396,1794,2024,512,2649,1922,2595,512,2112,1857,2528,512,2790,2079,1288,512,2634,2143,1908,512,1281,1640,2724,512,1664,1731,2169,512,2730,1791,1919,512,2185,1917,1802,512,2398,1795,1549,512,2098,2605,2361,512,2400,2328,1288,512,1568,2787,2810,512,1281,1858,1923,512,1543,2803,2182,512,2497,1859,2397,512,2758,1918,1988,512,1803,2158,2598,512,2180,1798,2053,512,1804,2464,1288,512,2166,2285,2167,512,1281,1640,2726,512,1664,1731,2116,512,2583,1791,1808,512,1840,2153,1921,512,2712,2384,1549,512,2046,1893,2448,512,2639,1801,1288,512,1568,2472,2062,512,1281,2145,2376,512,1543,1909,2150,512,2077,2366,2731,512,2162,1794,2024,512,2168,1922,2766,512,2407,1857,2117,512,2100,2240,1288,512,2186,2809,1908,512,1281,1640,2242,512,1664,1731,2359,512,2587,1791,1919,512,2184,1917,1802,512,2643,1795,1549,512,2172,2394,2635,512,2171,2187,1288,512,1568,2095,2163,512,1281,1858,1923,512,1543,2450,2749,512,2181,1859,2160,512,2183,1918,1988,512,1803,2161,2778,512,2403,1798,2791,512,1804,2775,1288,512,2355,2156,2362,512,1281,1640,2380,512,1664,1731,2052,512,2170,1791,1808,512,1840,2805,1921,512,2402,2741,1549,512,2046,1893,2101,512,2159,1801,1288,512,1568,2247,2769,512,1281,2739,2780,512,1543,1909,2737,512,2097,2445,2596,512,2757,1794,2024,512,2444,1922,2599,512,2112,1857,2804,512,2744,2079,1288,512,2707,2143,1908,512,1281,1640,2782,512,1664,1731,2169,512,2742,1791,1919,512,2185,1917,1802,512,2718,1795,1549,512,2098,2322,2504,512,2581,2350,1288,512,1568,2323,2597,512,1281,1858,1923,512,1543,2512,2182,512,2763,1859,2808,512,2360,1918,1988,512,1803,2158,2358,512,2180,1798,2053,512,1804,2603,1288,512,2166,2285,2167,512,1281,1640,2767,512,1664,1731,2116,512,2788,1791,1808,512,1840,2153,1921,512,2708,2723,1549,512,2046,1893,2717,512,2602,1801,1288,512,1568,2480,2062,512,1281,2145,2711,512,1543,1909,2150,512,2077,2338,2762,512,2162,1794,2024,512,2168,1922,2447,512,2334,1857,2117,512,2100,2240,1288,512,2186,2321,1908,512,1281,1640,2242,512,1664,1731,2729,512,2633,1791,1919,512,2184,1917,1802,512,2710,1795,1549,512,2172,2375,2789,512,2171,2187,1288,512,1568,2095,2163,512,1281,1858,1923,512,1543,2374,2446,512,2181,1859,2160,512,2183,1918,1988,512,1803,2161,2751,512,2413,1798,2529,512,1804,2344,1288,512,2404,2156,2786,512,1281,1640,2641,512,1664,1731,2052,512,2170,1791,1808,512,1840,2395,1921,512,2586,2319,1549,512,2046,1893,2101,512,2159,1801,1288,512,1568,2247,2773,512,1281,2365,2410,512,1543,1909,2781,512,2097,2411,2740,512,2396,1794,2024,512,2734,1922,2733,512,2112,1857,2528,512,2593,2079,1288,512,2648,2143,1908,512,1281,1640,2770,512,1664,1731,2169,512,2714,1791,1919,512,2185,1917,1802,512,2398,1795,1549,512,2098,2801,2361,512,2400,2328,1288,512,1568,2783,2713,512,1281,1858,1923,512,1543,3063,2182,512,2497,1859,2397,512,2794,1918,1988,512,1803,2158,2772,512,2180,1798,2053,512,1804,2464,1288,512,2166,2285,2167,512,1281,1640,2764,512,1664,1731,2116,512,2620,1791,1808,512,1840,2153,1921,512,2716,2384,1549,512,2046,1893,2448,512,2722,1801,1288,512,1568,2472,2062,512,1281,2145,2376,512,1543,1909,2150,512,2077,2366,2709,512,2162,1794,2024,512,2168,1922,2735,512,2407,1857,2117,512,2100,2240,1288,512,2186,2779,1908,512,1281,1640,2242,512,1664,1731,2359,512,2705,1791,1919,512,2184,1917,1802,512,2642,1795,1549,512,2172,2394,2645,512,2171,2187,1288,512,1568,2095,2163,512,1281,1858,1923,512,1543,2450,2771,512,2181,1859,2160,512,2183,1918,1988,512,1803,2161,2585,512,2403,1798,2619,512,1804,2777,1288,512,2355,2156,2362,512,1281,1640,2380,512,1664,1731,2052,512,2170,1791,1808,512,1840,2811,1921,512,2402,2601,1549,512,2046,1893,2101,512,2159,1801,1288,512,1568,2247,2719,512,1281,2747,2776,512,1543,1909,2725,512,2097,2445,2765,512,2638,1794,2024,512,2444,1922,2774,512,2112,1857,2727,512,2644,2079,1288,512,2800,2143,1908,512,1281,1640,2580,512,1664,1731,2169,512,2646,1791,1919,512,2185,1917,1802,512,2588,1795,1549,512,2098,2322,2504,512,2623,2350,1288,512,1568,2323,2721,512,1281,1858,1923,512,1543,2512,2182,512,2746,1859,2798,512,2360,1918,1988,512,1803,2158,2358,512,2180,1798,2053,512,1804,2745,1288,512,2166,2285,2167,512,1281,1640,2806,512,1664,1731,2116,512,2796,1791,1808,512,1840,2153,1921,512,2582,2761,1549,512,2046,1893,2793,512,2647,1801,1288,512,1568,2480,2062,512,1281,2145,2738,512,1543,1909,2150,512,2077,2338,2715,512,2162,1794,2024,512,2168,1922,2447,512,2334,1857,2117,512,2100,2240,1288,512,2186,2321,1908,512,1281,1640,2242,512,1664,1731,2795,512,2750,1791,1919,512,2184,1917,1802,512,2732,1795,1549,512,2172,2375,2604,512,2171,2187,1288,512,1568,2095,2163,512,1281,1858,1923,512,1543,2374,2446,512,2181,1859,2160,512,2183,1918,1988,512,1803,2161,2813,512,2413,1798,2529,512,1804,2344,1288,512,2404,2156,2743,512,1281,1640,2748,512,1664,1731,2052,512,2170,1791,1808,512,1840,2395,1921,512,2637,2319,1549,512,2046,1893,2101,512,2159,1801,1288,512,1568,2247,2812,512,1281,2365,2410,512,1543,1909,2799,512,2097,2411,2802,512,2396,1794,2024,512,2649,1922,2595,512,2112,1857,2528,512,2790,2079,1288,512,2634,2143,1908,512,1281,1640,2724,512,1664,1731,2169,512,2730,1791,1919,512,2185,1917,1802,512,2398,1795,1549,512,2098,2605,2361,512,2400,2328,1288,512,1568,2787,2810,512,1281,1858,1923,512,1543,2803,2182,512,2497,1859,2397,512,2758,1918,1988,512,1803,2158,2598,512,2180,1798,2053,512,1804,2464,1288,512,2166,2285,2167,512,1281,1640,2726,512,1664,1731,2116,512,2583,1791,1808,512,1840,2153,1921,512,2712,2384,1549,512,2046,1893,2448,512,2639,1801,1288,512,1568,2472,2062,512,1281,2145,2376,512,1543,1909,2150,512,2077,2366,2731,512,2162,1794,2024,512,2168,1922,2766,512,2407,1857,2117,512,2100,2240,1288,512,2186,2809,1908,512,1281,1640,2242,512,1664,1731,2359,512,2587,1791,1919,512,2184,1917,1802,512,2643,1795,1549,512,2172,2394,2635,512,2171,2187,1288,512,1568,2095,2163,512,1281,1858,1923,512,1543,2450,2749,512,2181,1859,2160,512,2183,1918,1988,512,1803,2161,2778,512,2403,1798,2791,512,1804,2775,1288,512,2355,2156,2362,512,1281,1640,2380,512,1664,1731,2052,512,2170,1791,1808,512,1840,2805,1921,512,2402,2741,1549,512,2046,1893,2101,512,2159,1801,1288,512,1568,2247,2769,512,1281,2739,2780,512,1543,1909,2737,512,2097,2445,2596,512,2757,1794,2024,512,2444,1922,2599,512,2112,1857,2804,512,2744,2079,1288,512,2707,2143,1908,512,1281,1640,2782,512,1664,1731,2169,512,2742,1791,1919,512,2185,1917,1802,512,2718,1795,1549,512,2098,2322,2504,512,2581,2350,1288,512,1568,2323,2597,512,1281,1858,1923,512,1543}; + +using Message; + +struct returnValue { + s32 value; + s32 bitindex; +}; + +fn readbits( ref auto data, s32 len,s32 bits, s32 bitindex){ + u16 huffy; + u64 ret= 0; + s32 value = 0; + s32 tmpVal = 0; + value = 0; + if(len-(bitindex >> 3)<4){ // for safety so we don't have to do checks everywhere else + return -1; + } + + for (u32 i = 0, i < bits, i = i +8) { + tmpVal = GETINT; + huffy =huffdecode[tmpVal]; + bitindex += (huffy>>8); + value |= (huffy & 0xFF) << i; + } + u32 valueU = value; + u32 bitindexU = bitindex; + ret = valueU | (bitindexU << 32); + return ret; +}; + +#define DECODERET value = ret & 2147483647;bitindex = ret >> 32; +fn readCsString(ref auto data, s32 len, s32 bitindex){ + s32 value = 0; + u64 ret = 0; + str test = ""; + + while(true){ + ret = readbits(data,len,8,bitindex);DECODERET + if(value != 3){ + break; + } + // configstring + ret = readbits(data,len,16,bitindex);DECODERET + u16 csNum = value; + + while(true){ + ret = readbits(data,len,8,bitindex);DECODERET + s8 val = value; + if(val <= 0){ + break; + } + char c = val; + test+=std::string::to_string(c); + } + return test; + } + return test; +}; + +fn measureCsString(ref auto data, s32 len, s32 bitindex){ + s32 value = 0; + u64 ret = 0; + + while(true){ + ret = readbits(data,len,8,bitindex);DECODERET + if(value != 3){ + break; + } + // configstring + ret = readbits(data,len,16,bitindex);DECODERET + u16 csNum = value; + + while(true){ + ret = readbits(data,len,8,bitindex);DECODERET + s8 val = value; + if(val <= 0){ + break; + } + } + return bitindex; + } + return bitindex; +}; + + +fn readString(ref auto data, s32 len, s32 bitindex){ + s32 value = 0; + u64 ret = 0; + str test = ""; + + while(true){ + + while(true){ + ret = readbits(data,len,8,bitindex);DECODERET + s8 val = value; + if(val <= 0){ + break; + } + char c = val; + test+=std::string::to_string(c); + } + return test; + } + return test; +}; + +struct Message { + le s32 messageNum; + le s32 len; + if(len != FINAL_DEMO_MESSAGE_LENGTH || messageNum != FINAL_DEMO_MESSAGE_NUMBER) { + u8 data[len]; + if( len>=10){ // should usually be true unless corrupted + + s32 bitindex = 0; + s32 value=0; + bitindex = 0; + u64 ret = readbits(data,len,32,bitindex);DECODERET + s32 reliableAcknowledge = value [[export]]; + + + + ret = readbits(data,len,8,bitindex);DECODERET + s32 firstCmd = value [[export]]; + + if(firstCmd == 7){ // snapshot + ret = readbits(data,len,32,bitindex);DECODERET + s32 serverTime = value [[export]]; + + } else if(firstCmd == 2){ + ret = readbits(data,len,32,bitindex);DECODERET + s32 serverTime = value [[export]]; + + s32 nextBitindex = measureCsString(data,len,bitindex); // silly to do the calc twice but sadly we can't return both a string and an integer, rip + str CS_SERVERINFO = readCsString(data,len,bitindex) [[export]]; + bitindex = nextBitindex; + str CS_SYSTEMINFO = readCsString(data,len,bitindex) [[export]]; + + } else if(firstCmd == 5){ + + ret = readbits(data,len,32,bitindex);DECODERET + s32 firstCommandSequence = value [[export]]; + str firstCommand = readString(data,len,bitindex) [[export]]; + + } + + } else{ + + std::warning("Message length under 10"); + } + } +} [[format("format::message_name")]]; + + + +namespace format { + fn message_name(ref Message value) { + if(value.len == FINAL_DEMO_MESSAGE_LENGTH && value.messageNum == FINAL_DEMO_MESSAGE_NUMBER) return std::string::to_string(value.messageNum) + ": " + "Demo End Message"; + if(value.len < 10) return std::string::to_string(value.messageNum) + ": Len < 10"; + if(value.firstCmd == 7) return std::string::to_string(value.messageNum) + ": Snapshot (" +std::string::to_string(value.serverTime)+ ")"; + return std::string::to_string(value.messageNum) + ": " + format::cmd_name(value.firstCmd); + }; + + fn firstCmd_name(s8 value) { + return std::string::to_string(value) + ": " + format::cmd_name(value); + }; + fn cmd_name(s8 value) { + if(value == 0) return "Bad"; + if(value == 1) return "NOP"; + if(value == 2) return "Gamestate"; + if(value == 5) return "Servercommand (probably with Snapshot)"; + if(value == 6) return "Download"; + if(value == 7) return "Snapshot"; + return "Can't detect type ("+std::string::to_string(value)+")"; + }; +} + +struct Q3Demo { + Message messages[while(!std::mem::eof())]; +}; + + +Q3Demo q3demo @ 0x00; \ No newline at end of file diff --git a/tests/patterns/test_data/q3demo.hexpat.dm_16 b/tests/patterns/test_data/q3demo.hexpat.dm_16 new file mode 100644 index 0000000000000000000000000000000000000000..f56af27ae9a368279fff85e740ae54cf25ea8ab3 GIT binary patch literal 19492 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 literal 0 HcmV?d00001