Windows üzerinde STL dosyalarını hızlı şekilde kontrol etmek için kullandığım Microsoft 3D Viewer'ın desteği sonlandırıldı. Uygulama çalışmaya devam ediyordu, ancak her açılışta örnek Bee modeli yükleniyor ve arayüzün üst bölümünde 3D Viewer'ın artık desteklenmediğini bildiren bir banner gösteriliyordu.
İlk amaç yeni bir program yazmak değildi. Bee modelini ve banner'ı mevcut uygulamadan kaldırmak yeterli olacaktı.
1. Bee.glb nereden geliyor?
İlk olarak uygulamanın kullanıcı profili altındaki geçici dosyaları incelendi. Başlangıç modeli burada görünüyordu:
C:\Users\...\AppData\Local\Packages\
Microsoft.Microsoft3DViewer_8wekyb3d8bbwe\
TempState\Assets\Bee.glb
Dosya yaklaşık 20 MB büyüklüğündeydi. Bee.glb silinip uygulama yeniden başlatıldığında dosya tekrar oluşturuldu. Aynı davranış ağ bağlantısı olmadan da devam etti.
Bu nedenle modelin internetten indirilmediği, uygulama paketinin içerisinde başka bir kopyasının bulunduğu açıktı.
Paket içerisindeki Assets\Archive.zip incelendiğinde kaynak bulundu:
Assets\Archive.zip
└── ModelShelf
└── Bee.glb
Archive içerisindeki Bee.glb ile TempState altında oluşturulan dosyanın büyüklüğü aynıydı:
Bee.glb
Length : 20177864 bytes
CompressedLength : 16013449 bytes
2. WindowsApps paketini doğrudan değiştirmemek
Kurulu uygulama şu dizindeydi:
C:\Program Files\WindowsApps\
Microsoft.Microsoft3DViewer_7.2602.8012.0_x64__8wekyb3d8bbwe
WindowsApps altındaki paket üzerinde doğrudan değişiklik yapmak yerine uygulamanın tamamı ayrı bir çalışma dizinine kopyalandı:
C:\Users\...\Desktop\3DViewerMod
Store paketine ait imza ve metadata dosyaları çalışma kopyasından çıkarıldı:
AppxSignature.p7x
AppxBlockMap.xml
AppxMetadata\
microsoft.system.package.metadata
Daha sonra AppxManifest.xml içerisindeki package identity değiştirildi:
Microsoft.Microsoft3DViewer
↓
Kadir.3DViewerMod
Developer Mode altında paket yeniden register edildi:
Add-AppxPackage -Register ".\AppxManifest.xml"
Sonuçta Microsoft Store tarafından kurulan orijinal uygulama yerinde kalırken, dosyaları değiştirilebilen bağımsız bir development package elde edildi.
3. Bee'yi boş bir GLB ile değiştirmek
İlk denemede Archive.zip içerisindeki Bee.glb yerine sözdizimsel olarak geçerli 112 byte'lık boş bir GLB yerleştirildi.
Bee kayboldu ancak uygulama bu kez model yükleme hatası verdi:
Couldn't load 3D model
Try again later.
Daha sonra tek üçgenden oluşan geçerli bir GLB oluşturuldu. Sonuç değişmedi. Bu test uygulamanın yalnızca GLB parser'ından geçen herhangi bir dosyayı başlangıç modeli olarak kabul etmediğini gösteriyordu.
Bu noktada yeni bir GLB üretmek yerine orijinal Bee.glb'nin yapısını koruyup yalnızca geometrisini etkisiz hale getirmek daha güvenliydi.
4. GLB dosyasının iç yapısı
GLB 2.0 dosyası temel olarak bir header, JSON chunk ve binary BIN chunk'tan oluşuyor:
+-----------------------+
| GLB Header |
| magic / version / len |
+-----------------------+
| JSON Chunk Header |
+-----------------------+
| JSON |
+-----------------------+
| BIN Chunk Header |
+-----------------------+
| Binary vertex data |
| indices |
| animation data |
| ... |
+-----------------------+
Orijinal Bee modeli analiz edildiğinde aşağıdaki yapı elde edildi:
Magic : 0x46546C67
Version : 2
TotalLength : 20177864
Meshes : 1
Nodes : 112
Accessors : 1934
BufferViews : 1938
Materials : 1
Animations : 3
Mesh'in POSITION verisini kullanan accessor bulundu:
POSITION accessor : 1
ComponentType : 5126 (FLOAT)
Type : VEC3
Count : 29614
BufferView : 1
ViewByteOffset : 310388
ViewByteLength : 355368
Boyut da beklenen değerle tam olarak eşleşiyordu:
29614 vertices
× 3 coordinates
× 4 bytes / float
-----------------
355368 bytes
5. Bee geometrisini tek noktaya çökertmek
GLB içerisindeki BIN chunk'ın başlangıç adresi hesaplandı:
jsonLength = *(uint32_t *)(GLB + 12)
binHeaderOffset = 20 + jsonLength
binDataOffset = binHeaderOffset + 8
POSITION verisinin gerçek dosya offset'i ise:
positionStart =
binDataOffset
+ bufferView.byteOffset
+ accessor.byteOffset
Bu dosyada hesaplanan değerler:
BIN data starts : 397920
POSITION starts : 708308
POSITION bytes : 355368
JSON, node yapısı, material, animation, index buffer ve diğer bütün veriler korundu. Yalnızca POSITION accessor'a ait 355368 byte sıfırlandı.
[Array]::Clear(
$bytes,
$positionStart,
$positionBytes
)
Böylece 29614 vertex'in tamamı:
(0.0, 0.0, 0.0)
koordinatına taşındı. Dosya hâlâ orijinal Bee modelinin bütün yapısına sahipti, ancak mesh geometrisi tek bir noktaya çökmüştü.
Değiştirilmiş GLB tekrar Archive.zip içerisine yerleştirildiğinde başlangıç modeli görünmedi ve uygulama herhangi bir model yükleme hatası da üretmedi.
6. Deprecation banner
Bee problemi çözüldükten sonra ikinci hedef destek sonlandırma banner'ıydı.
InfoBannerPromptAfterText
InfoBannerPromptBeforeText
InfoBannerDialogTitleAfter
InfoBannerDialogTitleBefore
InfoBannerDialogBodyAfter
InfoBannerDialogBodyBefore
InfoBannerDialogCloseButtonText
Mevcut banner metni InfoBannerPromptAfterText tarafından
sağlanıyordu. Ancak resource string'i silmek yalnızca metni ortadan
kaldıracaktı. Banner'ın kapladığı alan ve container ekranda kalacaktı.
7. 3DViewer.dll içerisinde banner kodu
Resource isimlerinin UTF-16 referansları 3DViewer.dll içerisinde bulundu.
Banner metnini hazırlayan fonksiyonlardan biri
0x180EB59E0 adresindeydi.
180eb59e0 push ...
...
180eb5a10 lea 0x1807cecb8,%rcx
; InfoBannerPromptBeforeText
call 0x1809f4880
mov %rax,%rsi
180eb5a25 lea 0x1807ced08,%rcx
; InfoBannerPromptAfterText
call 0x1809f4880
mov %rax,%rdi
...
180eb5a5d test %al,%al
180eb5a5f cmove %rdi,%rsi
180eb5a63 mov $0x1,%r8b
180eb5a66 mov %rsi,%rdx
180eb5a69 call 0x180dce7b0
180eb5a71 mov 0x3a0(%r14),%rdx
...
180eb5a9d call 0x180ad3d40
ret
Kod hem Before hem de After metnini yüklüyor, çalışma zamanındaki duruma göre
birini seçiyor ve this + 0x3A0 alanındaki XAML nesnesine
uyguluyordu.
Üst seviye initializer içerisindeki çağrı da bulundu:
180edd9e9 call 0x180e97aa0
180edd9f1 call 0x180eb5be0
180edd9f9 call 0x180eb5970
180edda01 call 0x180eb59e0
Son çağrı geçici olarak NOP ile değiştirildi:
VA : 0x180EDDA01
File offset : 0xEDAA01
Original:
E8 DA 7F FD FF
Patched:
90 90 90 90 90
Test sonucu fonksiyonun görevi konusunda doğrudan doğrulama sağladı: banner metni kayboldu ancak kırmızı container ekranda kaldı.
8. Generated XAML
Bir sonraki adım compiled XAML tarafına indi. Uygulamada fiziksel XAML dosyaları bulunmuyordu. UI bağlantıları 3DViewer.dll içerisinde generated code olarak bulunuyordu.
İlgili Connect fonksiyonu 0x180EB4C40 adresindeydi:
180eb4c40 push ...
180eb4c4b mov %rcx,%rdi
180eb4c4e lea -0x1(%rdx),%eax
; connectionId - 1
180eb4c51 cmp $0x2b,%eax
; 43 connection IDs
180eb4c54 jae 0x180eb54aa
180eb4c5a movslq %eax,%rdx
180eb4c5d lea 0x18032e220,%rax
; jump table
180eb4c64 movslq (%rax,%rdx,4),%rdx
180eb4c68 add %rax,%rdx
180eb4c6b jmp *%rdx
Bu klasik generated-XAML Connect(connectionId, target)
dispatch yapısıydı.
Banner metninde kullanılan this + 0x3A0 member'ı connection ID
41 tarafından atanıyordu:
180eb5349 mov %r8,%rcx
180eb534c lea 0x18001f968,%rdx
180eb5353 call *0x180749b20
180eb5359 mov %rax,%rdx
180eb535c lea 0x3a0(%rdi),%rcx
180eb5363 call *0x180749af0
180eb5369 jmp 0x180eb54aa
Komşu XAML member'ları da aynı dispatch içerisinde görünüyordu:
Connection ID 39 → this + 0x390
Connection ID 40 → this + 0x398
Connection ID 41 → this + 0x3A0
Connection ID 42 → event-connected control
Connection ID 43 → event-connected control
Örneğin ID 40:
180eb5324 mov %r8,%rcx
180eb5327 lea 0x18005e630,%rdx
180eb532e call *0x180749b20
180eb5334 mov %rax,%rdx
180eb5337 lea 0x398(%rdi),%rcx
180eb533e call *0x180749af0
Bu aşamada banner'ın text element'i ile surrounding XAML controls arasındaki bağlantılar incelenebiliyordu. Ancak yapılmakta olan iş artık STL görüntülemek için gereken işin çok ötesine geçmişti.
9. Burada reverse engineering'i bırakmak
Başlangıç modeli için GLB vertex buffer değiştirilmiş, Store paketinin development copy'si oluşturulmuş, resources.pri incelenmiş, PE section adresleri hesaplanmış, 3DViewer.dll disassemble edilmiş ve generated XAML connection table'a kadar inilmiştir.
Ama ihtiyaç değişmemişti:
Open STL.
Rotate model.
Inspect geometry.
Bu nedenle 3D Viewer üzerinde çalışmaya devam etmek yerine aynı işi yapan küçük bir native Windows programı yazmak daha uygun hale geldi.
10. Native STL Viewer
Yeni uygulama C++17, Win32 API, Windows Common Controls ve OpenGL kullanılarak yazıldı.
STLViewer.exe
│
├── Win32
│ ├── HWND
│ ├── HMENU
│ └── message loop
│
├── comctl32.dll
│ ├── ToolbarWindow32
│ ├── SysTreeView32
│ ├── SysListView32
│ └── msctls_statusbar32
│
├── uxtheme.dll
│
├── WGL
│ └── OpenGL context
│
└── STL loader
├── Binary STL
└── ASCII STL
Qt, Electron, CEF, WinUI, HTML/CSS veya .NET UI katmanı kullanılmadı. Viewport da ayrı bir native child HWND üzerinde çalışan WGL context'tir.
11. Binary STL parser
Binary STL formatı yeterince basit olduğu için ayrı bir model import framework'ü kullanılmadı.
80 bytes header
4 bytes uint32 triangle count
for each triangle:
12 bytes normal (3 × float32)
12 bytes vertex 1 (3 × float32)
12 bytes vertex 2 (3 × float32)
12 bytes vertex 3 (3 × float32)
2 bytes attribute
---------------------------------
50 bytes per triangle
Binary STL kontrolünde beklenen dosya büyüklüğü:
expected_size =
84 + triangle_count * 50
ile gerçek dosya boyutu karşılaştırılıyor. Eşleşme yoksa parser ASCII STL okumayı deniyor.
12. İlk build
İlk build'de iki küçük Win32/CMake problemi çıktı.
Mouse koordinatlarında kullanılan:
GET_X_LPARAM
GET_Y_LPARAM
makroları için windowsx.h eklenmesi gerekiyordu:
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
İkinci problem manifest'in hem resource script hem de Visual Studio tarafından eklenmesiydi:
CVTRES : fatal error CVT1100:
duplicate resource. type:MANIFEST, name:1
LINK : fatal error LNK1123:
failure during conversion to COFF
Tekrarlanan manifest resource kaldırıldıktan sonra executable oluşturuldu.
13. İlk STL testi
İlk gerçek test dosyası:
sadboys_mirror_battery_cover_mod.STL
Dosya binary STL olarak algılandı ve 8712 triangle sorunsuz yüklendi. Hesaplanan bounding dimensions:
X : 43.501 mm
Y : 25.244 mm
Z : 49.200 mm
Triangles : 8712
Format : Binary STL
File size : 425 KB
Version 0.1 bu aşamada binary ve ASCII STL açabiliyor, modeli OpenGL ile çizebiliyor, orbit ve zoom yapabiliyor, shaded/wireframe görünüm sunuyor, grid ve XYZ axes gösterebiliyor ve model boyutlarıyla triangle sayısını hesaplayabiliyor.
14. Arayüz
Arayüz için yeni bir UI framework kullanılmadı. Program geleneksel Windows desktop uygulaması olarak tutuldu.
Ana pencere ve bütün yardımcı kontroller Win32/Common Controls. Bu nedenle menü, TreeView, ListView, status bar ve pencere davranışı Windows tarafından çiziliyor.
Geliştirme sırasında hedeflenen arayüz yoğunluğu Windows 7 / Office 2010 dönemi desktop utility uygulamalarına yakın tutuldu: küçük toolbar ikonları, standart menüler, sol model/property paneli, büyük viewport ve status bar.
15. Mevcut durum
Proje şu anda ilk çalışan prototip aşamasında. Renderer bilinçli olarak legacy OpenGL immediate mode kullanıyor. Bu, ilk aşamada STL parser ve Win32 shell'i renderer optimizasyonundan bağımsız geliştirmeyi kolaylaştırıyor.
Sonraki adımlar arasında gerçek fit-to-view hesabı, pan, daha kontrollü orbit, splitter, toolbar image list ve daha sonra gerekirse VBO/VAO tabanlı renderer bulunuyor.
Programın kapsamı değişmeyecek:
No cloud.
No account.
No AI.
No telemetry.
No bees.
Opens STL files.





