2022-02-03
Här är ett litet försök att börja förstå hur WonderWare InTouch 9.5 sparar fönsterdata. För skojs skull har jag valt att endast använda standardverktyg som följer med de flesta Linux-distributioner, som GNU-verktygen diff, stat, grep, printf, dd, tail och head. Och BASH förstås. Samt xxd, som inte tillhör GNU, men som är vitt spridd och följer om inte annat med vim (tror jag).
När man sparar någonting, uppdateras INTOUCH.INI rad 111. Värdet efter ApplicationVersionNo= ökas med 1.
Ex:
ApplicationVersionNo=3284
ändras till:
ApplicationVersionNo=3285
Även app.ver ändras.
d50c
innebär 0x0cd5 == 3285.
Om man bara gör någon grafisk ändring ändras inte innehållet i linkdefs.ini men tidsstämpeln uppdateras.
Ingen skillnad här, endast tidsstämpel.
Använder fönster “Temp” (win00355.win) som endast innehåller en rektangel (som är gul). Laborerar lite, sparar och kopierar filen till kataloger som jag kallar:
temp1: Fyrkant
temp2: Fyrkant flyttat nedåt en pixel
temp3: Fyrkant flyttat uppåt en pixel (tillbaka till temp1)
Dessa borde vara identiska, men byte 116 har ändrats från 26 till 28. I temp2 var värdet 27. Detta är säkert ett versionsnummer som ökar med 1 varje gång fönstret sparas.
$ diff <(xxd temp1/win00355.win) <(xxd temp3/win00355.win)
8c8
< 00000070: 1200 0000 2600 0000 1a00 0000 1300 0000 ....&...........
---
> 00000070: 1200 0000 2800 0000 1a00 0000 1300 0000 ....(...........
$ xxd -s 116 -l 1 temp1/win00355.win
00000074: 26 &
$ xxd -s 116 -l 1 temp2/win00355.win
00000074: 27 '
$ xxd -s 116 -l 1 temp3/win00355.win
00000074: 28 (
$ diff <(xxd temp1/win00355.win) <(xxd temp2/win00355.win)
8c8
< 00000070: 1200 0000 2600 0000 1a00 0000 1300 0000 ....&...........
---
> 00000070: 1200 0000 2700 0000 1a00 0000 1300 0000 ....'...........
45,46c45,46
< 000002c0: 0000 3700 0000 8a00 0000 5200 0000 4001 ..7.......R...@.
< 000002d0: 0000 ac00 0000 0c00 0000 2f00 0000 0000 ........../.....
---
> 000002c0: 0000 3700 0000 8a00 0000 5300 0000 4001 ..7.......S...@.
> 000002d0: 0000 ad00 0000 0c00 0000 2f00 0000 0000 ........../.....
Byte 0x74 (116) skiljer sig, men det är förmodligen versionsnummer (se ovan).
Byte 0x2ca och 0x2d2 skiljer sig med 1. Någon av dessa borde vara Y-position.
I WindowMaker nere till höger syns koordinaterna när man markerat ett objekt. För temp1 gäller:
X: 138
Y: 82
W: 182
H: 90
Yay! Byte 0x2ca = 0x52 = 82. Så detta är Y-koordinat för aktuellt objekt.
Byte 0x2d2 = 0xac = 172. 172 - 82 = 90. Så detta är höjden för aktuellt objekt.
Ska prova flytta ned rektangeln 5 steg för att bekräfta. Spara till temp4.
$ diff <(xxd temp1/win00355.win) <(xxd temp4/win00355.win)
8c8
< 00000070: 1200 0000 2600 0000 1a00 0000 1300 0000 ....&...........
---
> 00000070: 1200 0000 2900 0000 1a00 0000 1300 0000 ....)...........
45,46c45,46
< 000002c0: 0000 3700 0000 8a00 0000 5200 0000 4001 ..7.......R...@.
< 000002d0: 0000 ac00 0000 0c00 0000 2f00 0000 0000 ........../.....
---
> 000002c0: 0000 3700 0000 8a00 0000 5700 0000 4001 ..7.......W...@.
> 000002d0: 0000 b100 0000 0c00 0000 2f00 0000 0000 ........../.....
0x52 -> 0x57 är diff på 5.
0xac -> 0xb1 är diff på 5.
Vad händer om jag klonar rektangeln och lägger nånstans?
Flyttade tillbaka originalrektangeln till temp1-position. Kopierade rektangeln och satte in på följande position och sparade som temp5:
X = 245
Y = 319
$ diff <(xxd temp1/win00355.win) <(xxd temp5/win00355.win)
8c8
< 00000070: 1200 0000 2600 0000 1a00 0000 1300 0000 ....&...........
---
> 00000070: 1200 0000 2a00 0000 1a00 0000 1300 0000 ....*...........
47,48c47,59
< 000002e0: 0000 0c00 0000 3300 0000 0000 0000 0c00 ......3.........
< 000002f0: 0000 0000 0000 0000 0000 ..........
---
> 000002e0: 0000 0c00 0000 2a00 0000 0000 0000 1600 ......*.........
> 000002f0: 0000 3f00 0000 0100 0000 0100 0000 3f00 ..?...........?.
> 00000300: 0000 0000 1800 0000 2d00 0000 0100 0000 ........-.......
> 00000310: 0400 0000 2d00 0000 0900 0000 2400 0000 ....-.......$...
> 00000320: 3500 0000 0100 0000 1000 0000 3500 0000 5...........5...
> 00000330: 0000 0000 0100 0000 0100 0000 ff83 ff00 ................
> 00000340: 2000 0000 3600 0000 0100 0000 0c00 0000 ...6...........
> 00000350: 3600 0000 0000 0000 ffff 0000 0400 0000 6...............
> 00000360: 2400 0000 3700 0000 0100 0000 1000 0000 $...7...........
> 00000370: 3700 0000 f500 0000 3f01 0000 ab01 0000 7.......?.......
> 00000380: 9901 0000 0c00 0000 2f00 0000 0000 0000 ......../.......
> 00000390: 0c00 0000 3300 0000 0000 0000 0c00 0000 ....3...........
> 000003a0: 0000 0000 0000 0000 ........
Här finns en del intressanta saker. Bytesen från 0x2e0 och framåt (0000 0c00 0000 3300…) har flyttat till slutet med start på 0x38e. Dessförinnan har det lags till ett antal rader som måste beskriva min nya rektangel.
$ stat -c %s temp/{1,5}/*.win
762
936
$ expr 936 - 762
174
Närmare bestämt 174 bytes verkar min nya rektangel kosta.
Tiotusenkronorsfrågan lyder: Vad består dessa 174 bytes av?
Först, låt oss undersöka mönster.
Vi antar att bytesekvensen
0000 0c00 0000 3300 0000 0000 0000 0c00 0000 0000 0000 0000 0000
talar om att filen är slut (vilket vore lite väl pratigt, så något mer
döljer sig förmodligen här, men låt oss strunta i det nu). I outputen
från senaste diffen ovan har vi identifierat 174 bytes med start på
0x2e0 som vår nya rektangel. Låt oss söka efter detta mönster och se vid
vilken offset vår första rektangel börjar.
$ grep -obUaP "\x00\x00\x0c\x00\x00\x00\x2a\x00" temp5/win00355.win
562:
*
736:
*
Här har vi två offsets: 562 (0x232) och 736 (0x2e0). Den sistnämnda stämmer ju väl med vad vi redan vet. Så första rektangeln startar på offset 0x232.
Ett litet tips!
printf
är ett ypperligt verktyg för att konvertera mellan hex och dec i terminalen.$ printf '%x' 562 232 $ printf '%d' 0x232 562
Toppen!
Få se var vi hamnar om vi utgår från offset 0x232 och hoppar fram 174 bytes. Finns det någonting mellan våra två rektanglar måntro?
$ xxd -s 0x232 -l 174 temp5/win00355.win
00000232: 0000 0c00 0000 2a00 0000 0000 0000 1600 ......*.........
00000242: 0000 3f00 0000 0100 0000 0100 0000 3f00 ..?...........?.
00000252: 0000 0000 1800 0000 2d00 0000 0100 0000 ........-.......
00000262: 0400 0000 2d00 0000 0900 0000 2400 0000 ....-.......$...
00000272: 3500 0000 0100 0000 1000 0000 3500 0000 5...........5...
00000282: 0000 0000 0100 0000 0100 0000 ff83 ff00 ................
00000292: 2000 0000 3600 0000 0100 0000 0c00 0000 ...6...........
000002a2: 3600 0000 0000 0000 ffff 0000 0400 0000 6...............
000002b2: 2400 0000 3700 0000 0100 0000 1000 0000 $...7...........
000002c2: 3700 0000 8a00 0000 5200 0000 4001 0000 7.......R...@...
000002d2: ac00 0000 0c00 0000 2f00 0000 0000 ......../.....
Kanske inte helt synligt för ögat sådär direkt, men tittar man noga ser man att en rad utgörs av 16 bytes (genom att räkna varje sifferpar eller genom att räkna ut t ex 0x2d2 - 0x2c2), vår rektangel slutar fyra siffror innan en full sista rad, vilket betyder 14 (=0xe) bytes efter den offset som anges före kolonet längst till vänster i början av raden, vilket i sig innebär att första byten efter vår andra rektangel ligger på…
$ printf '%x' $((0x2d2 + 0xe))
2e0
Den känner man igen! OK, så för att summera. Först hade vi en rektangel. Den tog 174 bytes och låg i slutet av filen direkt före de 26 bytes som vi tror signalerar att filen är slut. Sen lade vi till en rektangel. Den hamnade då direkt efter sin kompis och 26-bytes-sekvensen fick gå sist i led.
Nu tycker jag det är hög tid att producera en klon direkt i filen och se om InTouch accepterar den!
Enter temp666.
Idén är följande:
X#2 - X#1
och Y
till hälften av Y#2 - Y#1
.Då borde vi få en tjusigt sned lineup av rektanglar. (Spoiler alert: joråsatte.. läs vidare!)
Vi vet sedan tidigare att Y-positionen för temp1 (Y#1) lagras på byte
0x2ca. Relativ rektangelposition kan då räknas ut med
0x2ca - 0x232
:
$ printf '%x' $((0x2ca - 0x232))
98
Då ska alltså Y#2 ligga vid 0x2e0 + 0x98 = 0x378
. Låt
oss kontrollera!
$ for offset in {0x2ca,0x378}; do xxd -s $offset -l 1 temp5/win00355.win; done
000002ca: 52 R
00000378: 3f ?
Första raden är Y-position för rektangel 1, som vi minns är 82 =
0x52. Check!
Andra raden ska då vara Y-position för rektangel 2. 0x3f = 63.
HUH!!??
Vår andra rektangel var ju längre NED än den första. Låt oss konsultera skärmdumpen ovan lite noggrannare.
Här ser vi att korrekt koordinat är 319 = 0x13f. Och inte 0x3f.
AHA!
Vi kollade endast en byte, men koordinaterna sparas så klart som en int16 dvs 2 bytes.
$ for offset in {0x2ca,0x378}; do xxd -s $offset -l 2 temp5/win00355.win; done
000002ca: 5200 R.
00000378: 3f01 ?.
Lägst signifikant byte är 0x01 och andra byten 0x3f;
0x013f
= 319
.
Så. Då har vi koordinat Y#1 ocgh Y#2 och kan därmed bestämma Y#3:
$ printf '%x' $(((0x13f - 0x52) / 2))
76
\[Y_{\#3} = \frac{Y_{\#2} - Y_{\#1}}{2} = \frac{\mathtt{0x13f} - \mathtt{0x52}}{2} = \mathtt{0x76}\]
Ja jast ja. Låt oss leta reda på X#1, som vi med hjälp av skärmdumpen
vet är 138 = 0x8a
. Om vi startar på första fyrkantens byte
0 (d v s byte 0x232) och begränsar oss inom fyrkantens 174 bytes - och
letar efter 0x8a så får vi fram det här.
$ tail -c +$((0x232+1)) temp5/win00355.win | head -c 174 | LANG=C grep -obUaP "\x8a"
148:
Obs! Här använder jag en kombination av
tail
ochhead
för att plocka ut alla bytes från 0x232 och 174 bytes framåt.tail
börjar inte räkna på 0 utan 1, det är därför jag plussar med 1.Sedan använder jag
grep
för att söka efter hex-innehållet8a
och returnerar byteoffset. LANG=C behövs, för annars fungerar det inte. :-)
Gissningvis lagras även X-koordinaten i en int16, så låt oss kolla 2 st bytes 148 bytes in (+0x94) på fyrkant #2 (0x2e0) och se om värdet verkar rimligt för en X-koordinat!
$ xxd -s $((0x2e0 + 0x94)) -l 2 temp5/win00355.win
00000374: f500 ..
$ printf '%d' 0xf5
245
Stämmer 245 överens med skärmdumpen?
JAAA!!!
Då har vi alltså en ny Y-koordinat och kan räkna ut X.
\[X_{\#3} = \frac{X_{\#2} - X_{\#1}}{2} = \frac{\mathtt{0xf5} - \mathtt{0x8a}}{2} = \mathtt{0x35}\]
Ergo:
\[X_{\#3} = \mathtt{0x35}\] \[Y_{\#3} = \mathtt{0x76}\]
Nu återstår alltså bara att skapa en ny rektangel.
Fyrkant 1 börjar på 0x232, fyrkant 2 på
0x232 + 0xae =
0x2e0
och vår
ny fyrkant 3 ska då börja på
0x2e0 + 0xae =
0x38e
.
Låt oss kopiera fyrkant 2 och klistra in direkt efter.
$ { head -c -26 temp5/win00355.win; tail -c +$((0x2e0+1)) temp5/win00355.win | head -c 174; tail -c 26 temp5/win00355.win ;} > temp666/win00355.win
Detta ser krångligt ut men är ganska enkelt.
Nu återstår att sätta in rätt värden för X och Y.
X-koordinaten ligger på +0x94 alltså på
0x38e + 0x94 = 0x422
.
Låt oss ta en kopia av filen så att vi har något att jämföra med när vi vill verifiera att det fungerar. :-)
$ cp temp5/win00355.win temp5/win00355.win.2
Sådär, dags att ändra X-koordinat.
$ printf '\x35\x0' | dd of=temp666/win00355.win bs=1 seek=$((0x422)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 0,00277203 s, 0,7 kB/s
Det ovanstående gör är sonika att ersätta byte 0x422 med värdet 0x35 och 0x423 med värde 0x0.
Låt oss kontrollera att det fungerade som tänkt…
$ diff <(xxd temp666/win00355.win.2) <(xxd temp666/win00355.win)
67c67
< 00000420: 0000 f500 0000 3f01 0000 ab01 0000 9901 ......?.........
---
> 00000420: 0000 3500 0000 3f01 0000 ab01 0000 9901 ..5...?.........
Wohoo!
Då blir det samma procedur med Y-koordinaten.
$ printf '\x76\x0' | dd of=temp666/win00355.win bs=1 seek=$((0x426)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 7,6225e-05 s, 26,2 kB/s
Och kontroll…
$ diff <(xxd temp666/win00355.win.2) <(xxd temp666/win00355.win)
67c67
< 00000420: 0000 3500 0000 7600 0000 ab01 0000 9901 ..5...v.........
---
> 00000420: 0000 f500 0000 3f01 0000 ab01 0000 9901 ......?.........
Utmärkt!
Hade InTouch gjort den här ändringen hade versionnummer både i .win-filen men även i några andra filer ändrats. Låt oss vara lite wild and crazy och strunta i det och se vad som händer! :-D
Kopierar filen och öppnar i WindowMaker…
OK. Så här bidde det hela.
WindowMaker klagade inte över att vi pillat med hens fil. Inte heller över att vi struntat i densammes allehanda versionnuffror. Däremot visade InTouch sitt missnöje på ett lite retsamt vis: genom att rendera ett monster!
Fick vi en till rektangel? Ja.
Fick vi till rätt koordinater? Faktiskt - ja. Se själv i skärmdumpen.
X=53=0x35, Y=118=0x76.
Så, varför är vår fyrkant så groteskt stor?
Bläddrar vi upp ända till rubriken Diff temp1 och temp3 ser vi klart och tydligt att det var två värden som ändrades när jag knuffade första fyrkanten en pixel på Y-axeln. Inte ett värde. Det ena värdet listade vi ju snabbt ut att det var Y-koordinaten. Men det andra värdet, som vi fullständigt gav fan i, verkar onekligen vara höjden. Vi måste nog alltså öka/minska lika mycket på höjden som på Y och förmodligen samma med bredd och X.
Genom att studera Diff temp1 och temp5 och skärmdumpen direkt ovanför diffen, kan vi lista ut saker och ting. I skärmdumpen hittar vi X=245, Y=319, W=182, H=90. I och med att höjden ändras med samma värde som Y-positionen när vi knuffar runt på vår fyrkant, är det rimligt att anta att det som sparas inte är höjden per se utan ytterligare en koordinat: höjden fås genom att ta denna koordinat minus Y. Få se huruvida detta antagande bär frukt.
Med Y=319 och H=90 ska alltså värdet 319 + 90 = 409 = 0x199 återfinnas nånstans. Fast som vi sett tidigare swappas bytesen, så vi letar efter 0x9901. Rimligtfinns ligger den nånstans i närheten av Y = 319 = 0x13f = 0x3f01.
$ diff <(xxd temp1/win00355.win) <(xxd temp5/win00355.win)
8c8
< 00000070: 1200 0000 2600 0000 1a00 0000 1300 0000 ....&...........
---
> 00000070: 1200 0000 2a00 0000 1a00 0000 1300 0000 ....*...........
47,48c47,59
< 000002e0: 0000 0c00 0000 3300 0000 0000 0000 0c00 ......3.........
< 000002f0: 0000 0000 0000 0000 0000 ..........
---
> 000002e0: 0000 0c00 0000 2a00 0000 0000 0000 1600 ......*.........
> 000002f0: 0000 3f00 0000 0100 0000 0100 0000 3f00 ..?...........?.
> 00000300: 0000 0000 1800 0000 2d00 0000 0100 0000 ........-.......
> 00000310: 0400 0000 2d00 0000 0900 0000 2400 0000 ....-.......$...
> 00000320: 3500 0000 0100 0000 1000 0000 3500 0000 5...........5...
> 00000330: 0000 0000 0100 0000 0100 0000 ff83 ff00 ................
> 00000340: 2000 0000 3600 0000 0100 0000 0c00 0000 ...6...........
> 00000350: 3600 0000 0000 0000 ffff 0000 0400 0000 6...............
> 00000360: 2400 0000 3700 0000 0100 0000 1000 0000 $...7...........
> 00000370: 3700 0000 f500 0000 3f01 0000 ab01 0000 7.......?.......
> 00000380: 9901 0000 0c00 0000 2f00 0000 0000 0000 ......../.......
> 00000390: 0c00 0000 3300 0000 0000 0000 0c00 0000 ....3...........
> 000003a0: 0000 0000 0000 0000 ........
Mycket riktigt. Höjdinformation lagras på fjärde int16 efter Y-koordinaten. På samma sätt lagras breddinformationen (X + W = 245 + 182 = 0x1ab = 0xab01) fyra int16 efter X-koordinaten (0xf5). Omräknat till relative offsets kan vi göra följande sammanställning.
X: +0x94
Y: +0x98
W: +0x9c
H: +0xa0
Så vi har alltså att göra med fyra int16 som ligger direkt efter varandra i ordningen X-koordinat, Y-koordinat, Bredd, Höjd.
För fyrkant 3 som startar på offset 0x38e gäller således:
X: 0x38e + 0x94 = 0x422
Y: 0x38e + 0x98 = 0x426
W: 0x38e + 0x9c = 0x42a
H: 0x38e + 0xa0 = 0x42e
Eftersom vi vill behålla samma bredd och höjd som moderrektangeln, som ju var 182 respektive 90, är det bara att sätta igång och plussa.
W = X + 182 = 0x35 + 0xb6 = 0xeb
H = Y + 90 = 0x76 + 0x5a = 0xd0
Låt oss skriva:
$ printf '\xeb\x0' | dd of=temp666/win00355.win bs=1 seek=$((0x42a)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 9,1291e-05 s, 21,9 kB/s
$ printf '\xd0\x0' | dd of=temp666/win00355.win bs=1 seek=$((0x42e)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 0,000121587 s, 16,4 kB/s
Och kopiera filen till InTouch-katalogen, starta om InTouch och…….
…och…
…och…
En fyrkant inte alls grotesk, utan lika näpen som sina syskon; ej längre en mink bland hermeliner, men kanske än odresserad hermelin bland… dresserade hermeliner? Hur som helst har fyrkanten exakt de dimensioner vi önskade, men den är felplacerad.
Och den är inte felplacerad på grund av att InTouch är ett värdelöst skitprogram, utan för att VI (inte jag) är en värdelös skitmatematiker.
Vi glömde nämligen plussa på X#1 och Y#1 när vi räknade ut X#3 och Y#3.
\[X_{\#3} = X_{\#1} + \frac{X_{\#2} - X_{\#1}}{2} = \mathtt{0x8a} + \frac{\mathtt{0xf5} - \mathtt{0x8a}}{2} = \mathtt{0xbf}\]
\[Y_{\#3} = Y_{\#1} + \frac{Y_{\#2} - Y_{\#1}}{2} = \mathtt{0x52} + \frac{\mathtt{0x13f} - \mathtt{0x52}}{2} = \mathtt{0xc8}\]
\[W_{\#3} = X_{\#3} + 182 = \mathtt{0xbf} + \mathtt{0xb6} = \mathtt{0x175} = \mathtt{0x7501}\]
\[H_{\#3} = Y_{\#3} + 90 = \mathtt{0xc8} + \mathtt{0x5a} = \mathtt{0x122} = \mathtt{0x2201}\]
Skriva lite…
$ printf '\xbf\x0' | dd of=temp666/win00355.win bs=1 seek=$((0x422)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 9,147e-05 s, 21,9 kB/s
$ printf '\xc8\x0' | dd of=temp666/win00355.win bs=1 seek=$((0x426)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 4,7636e-05 s, 42,0 kB/s
$ printf '\x75\x01' | dd of=temp666/win00355.win bs=1 seek=$((0x42a)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 9,8921e-05 s, 20,2 kB/s
$ printf '\x22\x01' | dd of=temp666/win00355.win bs=1 seek=$((0x42e)) count=2 conv=notrunc
2+0 records in
2+0 records out
2 bytes copied, 0,000204353 s, 9,8 kB/s
Kopiera lite…
Ooooch….