Tumgik
#meg also made this lovely mb <3
burstingsunrise · 1 year
Photo
Tumblr media Tumblr media Tumblr media Tumblr media Tumblr media Tumblr media Tumblr media Tumblr media Tumblr media
carrot correspondence 
by meg @kaleidoscopeminds  and molly @burstingsunrise
pairing: cake rating: teen words: 15,164 cw: alcohol/drinking
His eyes. Calum is losing seconds if not weeks looking at them. They’re a little heavy and a lot hazy, framed with such ridiculously long lashes, and they glitter and shine at Calum as he pauses, with his lips already half turned up like he’s prepared to be delighted at any of Calum’s answers.  Calum entertains an errant passing thought about whether the bride would be willing to go for a gold glitter dusting on top of the piped flower design for the godforsaken carrot cake and—  The fucking carrot cake. Fuck.
welcome to the first crossover event in the mmccu!!! (meg and molly cake cinematic universe, trademarked by sam.) we wrote a cake fic about cake together, because what else would it be? more info in the a/n!
41 notes · View notes
allsassnoclass · 1 year
Note
yes good hello!!! may i know about 7, 10, 11, 13, and 25! and i love you sm :) xoxo
@clumsyclifford hi bella! thanks for asking love you too <3
7. What’s a topic you would like to explore? truth and trust! and the intersection of those two! and how to trust someone who isn't truthful and how to tell the truth to someone you don't know if you trust and how to trust someone again when you thought you knew the true them and have been proven wrong.
10. Do you always have a clear image before starting a piece? usually kind of! for fics, i have a general vision and it becomes and increasingly and increasingly clear. i usually know the beginning and the end for sure, with a few touchstone moments in the middle. for moodboards i have a pretty clear image, which can be an issue when i can't find the pictures i want and am not sure how to pivot. all of my moodboards end up looking very different from what i intially envision lol
11. What part of the process of creating brings you the most joy? probably brainstorming tbh! letting plots or ideas come to me and figuring out what's happening and getting to the point where i feel confident enough to start. it also kind of stresses me out because i have so many ideas, but i really like creating worlds and coming up with stories or envisioning moodboards.
13. Is there something you’d like to see created but you know you can’t/won’t do yourself? short answer: no <3 if i can envision something enough to want it, i envision it enough to want to make it myself. i always most enjoy other people's creations when it's something i myself would've never thought of or been able to visualize, such as megs's faking the moon landing fic. i never would've come up with that idea myself, so i enjoy reading it so much more! same with all of my moodboard ideas, with the exception of some moodboards for my fics (like the black coffee and sulfur one that maya made <3) because for my own fics i sometimes draw a huge blank on what should go in a mb, which doesn't typically happen when making a mb for someone else's fic or for my pairings as albums series. if i have an idea for something, i typically want to make it myself rather than have someone else make it
25. Tell us a little known fact about something you’ve made uhhhhhhhhhhh omg. what am i going to say. what have i made that i haven't talked extensively about. OH! in pas de deux harry styles was originally supposed to be a character. i had a scene where michael called him on the phone. they were exes. they danced together at abt. however i wrote him out lol he isn't mentioned in the fic anymore. it just didn't fit.
2 notes · View notes
scabopolis · 3 years
Text
lv au week, day 2: super heroes
Title: superbloom Fandom: Veronica Mars Rating: PG-13 for content, R for swears (Veronica writes in her feelings journal and really lets those swears go) Pairing: Logan Echolls/Veronica Mars Other Characters: Mentions of Mac and Meg Additional Tags: Secret identity (again?! what?), unnecessary epistolary literature (does a journal count as epistolary?), half-baked world building, a vague understanding of superhero lore Word Count: ~1,075 Day 1 | Day 2 | Day 3 | Day 4 | Day 5 | Day 6 | Day 7
***
Again, written solely because @cubbiegirl and @marshmellowbobcat are earth angels. I even added a title this time so that MB doesn’t have to come up with one. 
Why did I write this as Veronica’s journal? Mostly because it’s a style which seems to be a fanfic right of passage and I haven’t tried it so...here we are.  
I probably owe a lot to other superhero universes and their world building (as in, referring to them as ‘supers’ ala The Incredibles). What are the full extent of Logan and Veronica’s powers? *shrug emoji*
***
Date: February 15 Tracked a low-level Kane agent to a tourist trap bistro in downtown San Diego. Place was packed. Is this the new thing? Shitty men forget to make Valentine’s Day reservations, so they make them for the day after? 
Thought I had the guy but when I mistimed my pulse he metamorphosed into a cockroach and scurried away. 
Best part of the night was the chocolate mousse I got to-go. Despite appearances within, the food wasn’t too bad. 
Date: February 17 Caught up to Cockroach. Real name is Abel Koontz. Slimy guy tried to escape again, but Mac outfitted me with a souped up Morph Choke as backup — emphasis on the choke. Gonna need to recalibrate that a bit, Mackenzie. 
Bonus! Forgot it was laundry day, so had to wear my backup uni. Next time I run into Meg out in the field I’ll have to ask her if she can see my underwear through it. 
Date: February 18 Finally got around to watching season two of The Boys. Where do they get this shit? 
If Piznarski brings up forming some sort of super team for the 7-millionth time at the summit this year, I’m siccing Clayton on him.
Date: February 21 Got into a fight with Mac. Technically my fault. 
I faded while in public, and look, I get it. 
Being detected while living as my alter would be bad. BUT!! Being forced to interact with my ex as he is on a date with a woman who legitimately looks like she could be my doppelganger is very bad. 
Date: February 22 Clarified with Mac: me fading in public did not worry her. Me tripping Leo on purpose as I faded was apparently a problem.
Date: February 24 Cockroach stood me up.
Date: February 25 For all that is good and holy if another fucking cocky cowboy of a super moves to Neptune, I am going to lose my shit. This newest one? Got in my way as I attempted to track Kane, Jr into a warehouse. Fucking Smirky McCowboy stepped in my way and assured me I didn’t want to follow. Something about a dozen armed guards with guns aimed at the door. MAYBE THAT WAS MY PLAN!
(Because, of course the guy ripped a powers page right out of Superman’s book and has x-ray vision.)
(And, his uni? Not hard to tell when a super has money. They’re always the worst.)
Date: February 28 Ran into Smirky McCowboy again. This time outside of a poker game hosted by a real estate developer with connections to Kane. Smirky said it was a coincidence. 
Still no sign of Cockroach. 
Date: March 2 FUCKING HELL. He beat me to it. AGAIN! How is he doing this? 
Date: March 4 Smirky’s name is apparently Logan. Which I learned because I was getting coffee at Willow Grove (just minding my business while covertly eavesdropping on previously mentioned real estate developer) when some guy sat at my table. Unprompted. Uninvited. UNWELCOME.  
Mask or no mask, THAT SMIRK. 
The more concerning thing is that his x-ray vision apparently sees through the skeletal enhancements Mac wired into my mask, meaning he ID’d me right away. 
Date: March 5 Why do they keep coming here? Doesn’t New York have more crime? Go there!
Mac says it’s my fault for busting the Fitzpatricks, outing Kane as the sociopath he is, and helping all those kids find their lost dogs. 
Okay, she only mentioned the first two, but I think the third has value. 
The point is! these hangers-on need to find their own territory.  
Date: March 6 Mac traced at least 70 arrests to Smirky in the Los Angeles area alone. I hate him.
Date: March 10 If he scoops another one of my cases…
Date: March 11 He calls me Supergirl. 
He calls me Supergirl and I want to punch him. 
Mac says Cockroach’s tracker is still active but the signal is being blocked.
Date: March 13 Saw Meg today. She took down her own parents. Fucking savage. God, I love her. She flew me up to American Plaza and we drank champagne to celebrate.  
Oh! And good news! She says my backup uni isn’t see through. 
Date: March 13 Woke up with this terrifying thought: can smirky see through my uni? 
He wouldn’t...right? I mean...he’s an asshole but I don’t think he’s a creep. 
Date: March 16 I punched him. 
Not for the underwear thing! He swears he’s never looked and I weirdly believe him. 
And I didn’t punch him as much as he walked into a pulse. Yes, fine. 
I maybe purposefully put up the pulse to see if his x-ray vision could detect it. But I didn’t tell him to walk into it. 
(He can’t detect it, btw. I did it a second time just to be sure.)
Date: March 18 Found Cockroach. Or, I guess I should say Koontz. He washed up on Dog Beach as his alter. 
Mac said it could be a coincidence but we both know that’s not true. 
Date: March 19 Did I do this? Did I get Cockroach killed? 
Date: March 19 Logan has very good alcohol. 
Date: March 20 Hangovers. Bad. 
Date: March 20 Logan makes very good pancakes. 
Date: March 21 Logan has seen me drunk, which means Logan must be destroyed. 
Date: March 22 In a certain light, the smirk isn’t so bad. 
Date: March 23 Oh god. 
Date: March 24 Oh god.
Do I like him?
Date: March 25 I hate myself. I’m a giant cliché. I apologize to all women supers everywhere. 
Logan swears he can’t see past the new enhancements Mac made to my uni.
He also asked me to dinner. Was tempted to 1) fade, and 2) run away, but I did neither. 
Date: March 26 Adding insult to injury, Smirky McCowboy really knows how to kiss.
Date: March 27 Mac says I shouldn’t be so hard on myself. 
Actually, what she said was that neither he nor I are all that pleasant to be around so we might be meant for each other. 
Date: March 28 He really knows how to do some other things, too. 
Date: March 29 Logan got me a present: surveillance footage of Kane’s mysterious second-in-command with Koontz the night before he was found.
He’s been upgraded to not the absolute worst. 
Date: March 29 To be perfectly clear: we are not a super team. 
Date: March 30 Logan and I are on surveillance detail tonight. 
Maybe I can convince him to stop for some chocolate mousse. 
40 notes · View notes
Tag Game
//was tagged by @nomadsuggestions. Most of the questions are just copied, but a few of them I changed because the others didn’t seem relevant or I didn’t want to answer them. 
Rules: Answer 30 questions and tag 20 blogs you would like to know better!
Name: Steven Grant Rogers
//Megan
Nicknames: Stevie, Steeb, any stupid nickname Bucky gave me 
// Meg, Meegan, MB, Meggie, Teeb, the Godspeed Teeb (a reference from the Martian plus Teeb)
Gender: Cis Male (because mod knows nothing about trans and therefore doesn’t know how to play one... as if I know how to play a man)
//Cis Female
Sexuality: Bisexual
//Bisexual, but questioning
Zodiac: Cancer
//Leo
Birthday: July 4th, 1918
//July 24th
Age: Nearly 100
//Nearly 17
Height: 6′2
//5′3 (I’m smol)
Hair Color: Blonde/Brown
//Brown with golden streaks because ya girl’s got some natural highlights
Eye Color: Blue with hints of green
//Green (but green eyes are the only eye color that can change colors, so mine change different shades and also change to blue a lot)
Favorite Bands/Solo Artists: Sam got me hooked on Martin Gaye. I also like a lot of 1940′s artists (Cliff Edwards, Jimmie Davis, Judy Garland, Hank Williams, Frank Sinatra, Billie Holiday)
//Fall Out Boy, Troye Sivan, EDEN, Ed Sheeran, Imagine Dragons, Bastille, Conor Maynard, Sleeping At Last, The Cab
Song Stuck In My Head: Bucky showed me a song called Little Talks by Of Monsters and Men, and it’s been stuck in my head
//Youngblood by 5SOS (even though I really don’t care for 5SOS due to some bad memories associated with them)
Last Movie I Saw: The Little Mermaid and Moana at movie night with Buck, Nat, and the nieces.
//Fallen Kingdom (I don’t care for the Jurassic series, my brother and dad do, but man, that movie made me love the series. Also my dad won’t stop making fun of me because I cried over a Brontosaurus.)
Last Thing I Googled: “Most Dangerous Drug”... you know why
//”Greenhouse Academy Season 3″ I was wondering if it was renewed yet/if there was a release date.. There was nothing to be found
Other Blogs: // @bigboypatriot, @bernardsuggestion, @tjhammondsuggestion
Do you get asks: Yes, but mostly from the murder nieces. 
//Mostly Courtney, but yes. I love them more when they’re from someone else though because the “murder nieces” don’t ask any real questions/say anything worthwhile. They just mention drug night or “naughty uncle Steve.” I mean, I love those too, but actual questions or messages mean more to me.
Following: I follow a lot of friends or people I’ve had association with.
//aka I follow suggestion blogs I’ve interacted with or want to interact with
Average Amount of Sleep: Each night is different depending on situations (like being kept up until four or having nightmares), but on a good night, I get about 8-10 hours. 
//I sleep heavy and when I don’t have to wake up early, I can easily sleep 10-12 hours. 
What I Am Wearing: T-Shirt and Sweatpants
//T-Shirt, leggings, nike running shoes
Dream Job: If I were to have another job, it would probably still be pretty unconventional. Animator or illustrator, maybe?
//I want to be a screenwriter, but also if that doesn’t work out, I would want to be a child psychiatrist.
Nationality: American with Irish-born parents
//American (with backgrounds of Irish and British)
Favorite Book: The Great Gatsby or The Grapes of Wrath
//The Great Gatsby is one of my favorites, but I have had a deep love for a series called the Wolves of Mercy Falls for a long time. 
Favorite Hobby: Drawing
//Writing
Favorite Shows: There is a lot I need to catch up on, but Parks and Rec is one that was highly recommended to me and turned out to be one of my favorites. Friends is a good one too. And one that is not so popular, Sun Records. Sadly, it got cancelled after the first season. 
//Riverdale is my number one favorite, but I also love HTGAWM, Sherlock, Stranger Things, MINDHUNTER, Scandal, Parks and Rec, The Office, Sun Records (rip), Eyewitness (rip), Conviction (rip), Agent Carter (rip), all the Arrowverse shows. 
Most Annoying Question You Are Asked: What was it like getting out of the ice? 
//How do you spell your name? or more annoyingly, them not asking and spelling it wrong. 
Favorite Place: Will always be Brooklyn
//Washington DC just has a place in my heart ever since my trip a couple months ago. 
Dream Trip: I would love to take a trip anywhere relaxing. Buck and I are currently taking a trip to Northern California. 
//London. I have wanted to go to London for years.
Play an Instrument: I tried to play piano when I was younger, but couldn’t get the hang of it. 
//Used to be able to play trumpet, took piano lessons when I was younger but forget everything
Favorite Food: It’s a boring answer but pizza. 
//I love pasta. Pretty much any kind. I just love it. 
Favorite Celebrity: Sebastian Stan because have you seen him? 
//Chris Evans
Favorite Movie: 12 strong is a pretty good one off the top of my head. 
//Any the Marvel movies, but besides that, I love the Great Gatsby (the one with Leo not Robert Redford) and Big Hero 6 (technically a Marvel movie, but whatever)
Lucky Number: 4? Maybe?
//24, I guess.
Things You Watch On Youtube: I like Buzzfeed Unsolved
//Buzzfeed Unsolved, Dan and Phil (FAVES), Good Mythical Morning (and a lot of others, but those are my currents/all time favorites)
I tag @whitewolfsuggestion @ask-natasha-anything @howardstarksuggestion @buckysuggestion @star-lord-suggestions @murderchildsuggestions @steverogersuggestions @buckybarnessuggestion @liliastan @killmonger-suggestions @skinnystevesuggestion @stevesuggestion @quake-daisy-suggestions @edwin-jarvis-suggestion @peterparkersuggestion @agentcartersuggestion @harleykeenersuggestions @korgsuggests @butterfingers-suggestion @dum-e-suggestion
Sorry if you were already tagged! 
5 notes · View notes
suzanneshannon · 4 years
Text
CSI: The case of the missing WAV audio files on the FAT32 SD Card
Buckle up kids, as this is a tale. As you may know, I have a lovely podcast at https://hanselminutes.com. You should listen.
Recently through an number of super cool random events I got the opportunity to interview actor Chris Conner who plays Poe on Altered Carbon. I'm a big fan of the show but especially Chris. You should watch the show because Poe is a joy and Chris owns every scene, and that's with a VERY strong cast.
I usually do my interviews remotely for the podcast but I wanted to meet Chris and hang out in person so I used my local podcasting rig which consists of a Zoom H6 recorder.
I have two Shure XLR mics, a Mic stand, and the Zoom. The Zoom H6 is a very well though of workhorse and I've used it many times before when recording shows. It's not rocket surgery but one should always test their things.
I didn't want to take any chances to I picked up a 5 pack of 32GIG high quality SD Cards. I put a new one in the Zoom, the Zoom immediately recognized the SD Card so I did a local recording right there and played it back. Sounds good. I played it back locally on the Zoom and I could hear the recording from the Zoom's local speaker. It's recording the file in stereo, one side for each mic. Remember this for later.
I went early to the meet and set up the whole recording setup. I hooked up a local monitor and tested again. Records and plays back locally. Cool. Chris shows up, we recorded a fantastic show, he's engaged and we're now besties and we go to Chipotle, talk shop, Sci-fi, acting, AIs, etc. Just a killer afternoon all around.
I head home and pull out the SD Card and put it into the PC and I see this. I almost vomit. I get lightheaded.
I've been recording the show for over 730 episodes over 14 years and I've never lost a show. I do my homework - as should you. I'm reeling. Ok, breathe. Let's work the problem.
Right click the drive, check properties. Breathe. This is a 32 gig drive, but Windows sees that it's got 329 MB used. 300ish megs is the size of a 30 minute long two channel WAV file. I know this because I've looked at 300 meg files for the last several hundred shows. Just like you might know roughly the size of a JPEG your camera makes. It's a thing you know.
Command line time. List the root directory. Empty. Check it again but "show all files," weird, there's a Mac folder there but maybe the SD Card was preformatted on a Mac.
Interesting Plot Point - I didn't format the SD card. I use it as it came out of the packaging from Amazon. It came preformatted and I accepted it. I tested it and it worked but I didn't "install my own carpet." I moved in to the house as-is.
What about a little "show me all folders from here down" action? Same as I saw in Windows Explorer. The root folder has another subfolder which is itself. It's folder "Inception" with no Kick!
G:\>dir /a Volume in drive G has no label. Volume Serial Number is 0403-0201 Directory of G:\ 03/12/2020 12:29 PM <DIR> 03/13/2020 12:44 PM <DIR> System Volume Information 0 File(s) 0 bytes 2 Dir(s) 30,954,225,664 bytes free G:\>dir /s Volume in drive G has no label. Volume Serial Number is 0403-0201 Directory of G:\ 03/12/2020 12:29 PM <DIR> 0 File(s) 0 bytes Directory of G:\ 03/12/2020 12:29 PM <DIR> 0 File(s) 0 bytes IT GOES FOREVER
Ok, the drive thinks there's data but I can't see it. I put the SD card back in the Zoom and try to play it back.
The Zoom can see folders and files AND the interview itself. And the Zoom can play it back. The Zoom is an embedded device with an implementation of the FAT32 file system and it can read it, but Windows can't. Can Linux? Can a Mac?
Short answer. No.
Hacky Note: Since the Zoom can see and play the file and it has a headphone/monitor jack, I could always plug in an analog 1/8" headphone cable to a 1/4" input on my Peavy PV6 Mixer and rescue the audio with some analog quality loss. Why don't I use the USB Audio out feature of the Zoom H6 and play the file back over a digital cable, you ask? Because the Zoom audio player doesn't support that. It supports three modes - SD Card Reader (which is a pass through to Windows and shows me the recursive directories and no files), an Audio pass-through which lets the Zoom look like an audio device to Windows but doesn't show the SD card as a drive or allow the SD Card to be played back over the digital interface, or its main mode where it's recording locally.
It's Forensics Time, Kids.
We have an 32 SD Card - a disk drive as it were - that is standard FAT32 formatted, that has 300-400 megs of a two-channel (Chris and I had two mics) WAV file that was recorded locally by the Zoom H6  audio reorder and I don't want too lose it or mess it up.
I need to take a byte for byte image of what's on the SD Card so I can poke and it and "virtually" mess with with it, change it, fix it, try again, without changing the physical.
"dd" is a command-line utility with a rich and storied history going back 45 years. Even though it means "Data Definition" it'll always be "disk drive" I my head.
How to clone a USB Drive or SD Card to an IMG file on Windows
I have a copy of dd for Windows which lets me get a byte for byte stream/file that represents this SD Card. For example I could get an entire USD device:
dd if=\\?\Device\Harddisk1\Partition0 of=c:\temp\usb2.img bs=1M --size --progress
I need to know the Harddisk number and Partition number as you can see above. I usually use diskpart for this.
>diskpart Microsoft DiskPart version 10.0.19041.1 Copyright (C) Microsoft Corporation. On computer: IRONHEART DISKPART> list disk Disk ### Status Size Free Dyn Gpt -------- ------------- ------- ------- --- --- Disk 0 Online 476 GB 0 B * Disk 1 Online 1863 GB 0 B * Disk 2 Online 3725 GB 0 B Disk 3 Online 2794 GB 0 B * Disk 8 Online 29 GB 3072 KB DISKPART> select disk 8 Disk 8 is now the selected disk. DISKPART> list part Partition ### Type Size Offset ------------- ---------------- ------- ------- Partition 1 Primary 29 GB 4096 KB
Looks like it's Disk 8 Partition 1 on my system. Let's get it all before I panic.
dd if=\\?\Device\Harddisk8\Partition1 of=c:\temp\ZOMG.img bs=1M --size --progress
IF and OF are input file and output file, and I will do it for the whole size of the SD Card. It's likely overkill though as we'll see in a second.
This file ended up being totally massive and hard to work with. Remember I needed just the first 400ish megs? I'll chop of just that part.
dd if=ZOMG.img of=SmallerZOMG.img bs=1M count=400
What is this though? Remember it's an image of a File System. It just bytes in a file. It's not a WAV file or a THIS file or a THAT file. I mean, it is if we decide it is, but in fact, a way to think about it is that it's a mangled envelope that is dark when I peer inside it. We're gonna have to feel around and see if we can rebuild a sense of what the contents really are.
Importing Raw Bytes from an IMG into Audition or Audacity
Both Adobe Audition and Audacity are audio apps that have an "Import RAW Data" feature. However, I DO need to tell Audition how to interpret it. There's lots of WAV files out there. How many simples were there? 1 channel? 2 channel? 16 bit or 32 bit? Lots of questions.
Can I just import this 4 gig byte array of a file system and get something?
Looks like something. You can see that the first part there is likely the start of the partition table, file system headers, etc. before audio data shows up. Here's importing as 2 channel.
I can hear voices but they sound like chipmunks and aren't understandable. Something is "doubled." Sample rate? No, I double checked it.
Here's 1 channel raw data import even though I think it's two.
Now THIS is interesting. I can hear audio at normal speed of us talking (after the preamble) BUT it's only a syllable at a time, and then a quieter version of the same syllable repeats. I don't want to (read: can't really) reassemble a 30 min interview from syllables, right?
Remember when I said that the Zoom H6 records a two channel file with one channel per mic? Not really. It records ONE FILE PER CHANNEL. A whateverL.wav and a whateverR.wav. I totally forgot!
This "one channel" file above is actually the bytes as they were laid down on disk, right? It's actually two files written simultaneously, a few kilobytes at a time, L,R,L,R,L,R. And here I am telling my sound software to treat this "byte for byte file system dump" as one file. It's two that were made at the same time.
It's like the Brundlefly. How do I tease it apart? Well I can't treat the array as a raw file anymore, it's not. And I want (really don't have the energy yet) to write my own little app to effectively de-interlace this image. I also don't know if the segment size is perfectly reliable or if it varies as the Zoom recorded.
NOTE: Pete Brown has written about RIFF/WAV files from Sound Devices records having an incorrect FAT32 bit set. This isn't that, but it's in the same family and is worth noting if you ever have an issue with a Broadcast Wave File getting corrupted or looking encrypted.
Whole helping me work this issue, Pete Brown tweeted a hexdump of the Directory Table so you can see the Zoom0001, Zoom0002, etc directories there in the image.
Let me move into Ubuntu on my Windows machine running WSL. Here I can run fdisk and get some sense of what this Image of the bad SD Card is. Remember also that I hacked off the first 0-400 Megs but this IMG file thinks it's a 32gig drive, because it is. It's just that's been aggressively truncated.
$ fdisk -u -l SmallerZOMG.img Disk SmallerZOMG.img: 400 MiB, 419430400 bytes, 819200 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x00000000 Device Boot Start End Sectors Size Id Type SmallerZOMG.img1 8192 61157375 61149184 29.2G c W95 FAT32 (LBA)
Maybe I can "mount" this IMG? I make a folder on Ubuntu/WSL2 called ~/recovery. Yikes, ok there's nothing there. I can take the sector size 512 times the Start block of 8192 and use that as the offset.
sudo mount -o loop,offset=4194304 SmallerShit.img recover/ $ cd recover/ $ ll total 68 drwxr-xr-x 4 root root 32768 Dec 31 1969 ./
Ali Mosajjal thinks perhaps "they re-wrote the FAT32 structure definition and didn't use a standard library and made a mistake," and Leandro Pereria postulates "what could happen is that the LFN (long file name) checksum is invalid and they didn't bother filling in the 8.3 filename... so that complying implementations of VFAT tries to look at the fallback 8.3 name, it's all spaces and figures out "it's all padding, move along."
Ali suggested running dosfsck on the mounted image and you can see again that the files are there, but there's like 3 root entries? Note I've done a cat of /proc/mounts to see the loop that my img is mounted on so I can refer to it in the dosfsck command.
$ sudo dosfsck -w -r -l -a -v -t /dev/loop3 fsck.fat 4.1 (2017-01-24) Checking we can access the last sector of the filesystem Boot sector contents: System ID " " Media byte 0xf8 (hard disk) 512 bytes per logical sector 32768 bytes per cluster 1458 reserved sectors First FAT starts at byte 746496 (sector 1458) 2 FATs, 32 bit entries 3821056 bytes per FAT (= 7463 sectors) Root directory start at cluster 2 (arbitrary size) Data area starts at byte 8388608 (sector 16384) 955200 data clusters (31299993600 bytes) 63 sectors/track, 255 heads 8192 hidden sectors 61149184 sectors total Checking file / Checking file / Checking file / Checking file /System Volume Information (SYSTEM~1) Checking file /. Checking file /.. Checking file /ZOOM0001 Checking file /ZOOM0002 Checking file /ZOOM0003 Checking file /ZOOM0001/. Checking file /ZOOM0001/.. Checking file /ZOOM0001/ZOOM0001.hprj (ZOOM00~1.HPR) Checking file /ZOOM0001/ZOOM0001_LR.WAV (ZOOM00~1.WAV) Checking file /ZOOM0002/. Checking file /ZOOM0002/.. Checking file /ZOOM0002/ZOOM0002.hprj (ZOOM00~1.HPR) Checking file /ZOOM0002/ZOOM0002_Tr1.WAV (ZOOM00~1.WAV) Checking file /ZOOM0002/ZOOM0002_Tr2.WAV (ZOOM00~2.WAV) Checking file /ZOOM0003/. Checking file /ZOOM0003/.. Checking file /ZOOM0003/ZOOM0003.hprj (ZOOM00~1.HPR) Checking file /ZOOM0003/ZOOM0003_Tr1.WAV (ZOOM00~1.WAV) Checking file /ZOOM0003/ZOOM0003_Tr2.WAV (ZOOM00~2.WAV) Checking file /System Volume Information/. Checking file /System Volume Information/.. Checking file /System Volume Information/WPSettings.dat (WPSETT~1.DAT) Checking file /System Volume Information/ClientRecoveryPasswordRotation (CLIENT~1) Checking file /System Volume Information/IndexerVolumeGuid (INDEXE~1) Checking file /System Volume Information/AadRecoveryPasswordDelete (AADREC~1) Checking file /System Volume Information/ClientRecoveryPasswordRotation/. Checking file /System Volume Information/ClientRecoveryPasswordRotation/.. Checking file /System Volume Information/AadRecoveryPasswordDelete/. Checking file /System Volume Information/AadRecoveryPasswordDelete/.. Checking for bad clusters.
We can see  them, but can't get at them with the vfat file system driver on Linux or with Windows.
The DUMP.exe util as part of mtools for Windows is amazing but I'm unable to figure out what is wrong in the FAT32 file table. I can run minfo on the Linux command land telling it to skip 8192 sectors in with the @@offset modifier:
$ minfo -i ZOMG.img@@8192S device information: =================== filename="ZOMG.img" sectors per track: 63 heads: 255 cylinders: 3807 mformat command line: mformat -T 61149184 -i ZOMG.img@@8192S -h 255 -s 63 -H 8192 :: bootsector information ====================== banner:" " sector size: 512 bytes cluster size: 64 sectors reserved (boot) sectors: 1458 fats: 2 max available root directory slots: 0 small size: 0 sectors media descriptor byte: 0xf8 sectors per fat: 0 sectors per track: 63 heads: 255 hidden sectors: 8192 big size: 61149184 sectors physical drive id: 0x80 reserved=0x0 dos4=0x29 serial number: 04030201 disk label=" " disk type="FAT32 " Big fatlen=7463 Extended flags=0x0000 FS version=0x0000 rootCluster=2 infoSector location=1 backup boot sector=6 Infosector: signature=0x41615252 free clusters=944648 last allocated cluster=10551
Ok, now we've found yet ANOTHER way to mount this corrupted file system. With mtools we'll use mdir to list the root directory. Note there is something wrong enough that I have to set mtools_skip_check=1 to ~/.mtoolsrc and continue.
$ mdir -i ZOMG.img@@8192S :: Total number of sectors (61149184) not a multiple of sectors per track (63)! Add mtools_skip_check=1 to your .mtoolsrc file to skip this test $ pico ~/.mtoolsrc $ mdir -i ZOMG.img@@8192S :: Volume in drive : is Volume Serial Number is 0403-0201 Directory for ::/ <DIR> 2020-03-12 12:29 1 file 0 bytes 30 954 225 664 bytes free
Same result. I can run mdu and see just a few folders. Note the ZOOMxxxx ones are missing here
$ mdu -i ZOMG.img@@8192S :: ::/System Volume Information/ClientRecoveryPasswordRotation 1 ::/System Volume Information/AadRecoveryPasswordDelete 1 ::/System Volume Information 5 ::/ 6
Now, ideally I want to achieve two things here.
Know WHY it's broken and exactly WHAT is wrong.
There's a nameless root directory here and I lack the patience and skill to manually hexdump and patch it.
Be able to copy the files out "normally" by mounting the IMG and, well, copying them out.
UPDATE #1 - I'm back after a few minutes of thinking again. If I do the 512*8192 offset again and visualize the FAT32 table in Hexdump/xxd like this:
xxd -seek 4194304 ZOMG.img | more 00400000: eb00 9020 2020 2020 2020 2000 0240 b205 ... ..@.. 00400010: 0200 0000 00f8 0000 3f00 ff00 0020 0000 ........?.... .. 00400020: 0010 a503 271d 0000 0000 0000 0200 0000 ....'........... 00400030: 0100 0600 0000 0000 0000 0000 0000 0000 ................ 00400040: 8000 2901 0203 0420 2020 2020 2020 2020 ..).... 00400050: 2020 4641 5433 3220 2020 0000 0000 0000 FAT32 ...... 00400060: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00400070: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00400080: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00400090: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 004000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 004000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 004000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
I can see I seek'ed to the right spot, as the string FAT32 is just hanging out. Maybe I can clip out this table and visualize it in a better graphical tool.
I could grab a reasonable (read: arbitrary) chunk from this offset and put it in a very small manageable file:
dd if=ZOMG.img ibs=1 skip=4194304 count=64000 > another.img
And then load it in dump.exe on Windows which is really a heck of a tool. It seems to be thinking thinking there's multiple FAT Root Entries (which might be why I'm seeing this weird ghost root). Note the "should be" parts as well.
FAT Root Entry (non LFN) (0x00000000) Name: ··· Extension: Attribute: 0x00 FAT12:reserved: 02 40 B2 05 02 00 00 00 00 F8 FAT32:reserved: 02 FAT32:creation 10th: 0x40 FAT32:creation time: 0x05B2 FAT32:creation date: 0x0002 FAT32:last accessed: 0x0000 FAT32:hi word start cluster: 0xF800 Time: 0x0000 (00:00:00) (hms) Date: 0x003F (1980/01/31) (ymd) Starting Cluster: 0x00FF (0xF80000FF) File Size: 8192 FAT Root Entry (non LFN) (0x00000020) Name: ····'··· Extension: ··· Attribute: 0x00 FAT12:reserved: 02 00 00 00 01 00 06 00 00 00 FAT32:reserved: 02 FAT32:creation 10th: 0x00 FAT32:creation time: 0x0000 FAT32:creation date: 0x0001 FAT32:last accessed: 0x0006 FAT32:hi word start cluster: 0x0000 Time: 0x0000 (00:00:00) (hms) Date: 0x0000 (1980/00/00) (ymd) Starting Cluster: 0x0000 (0x00000000) <--- should be 0x0002 or higher. File Size: 0 FAT Root Entry (non LFN) (0x00000040) Name: ··)···· Extension: Attribute: 0x20 Archive FAT12:reserved: 20 20 20 20 20 20 46 41 54 33 FAT32:reserved: 20 FAT32:creation 10th: 0x20 FAT32:creation time: 0x2020 FAT32:creation date: 0x2020 FAT32:last accessed: 0x4146 FAT32:hi word start cluster: 0x3354 Time: 0x2032 (04:01:18) (hms) Date: 0x2020 (1996/01/00) (ymd) Starting Cluster: 0x0000 (0x33540000) File Size: 0 FAT Root Entry (non LFN) (0x00000060) Name: ········ Extension: ··· Attribute: 0x00 FAT12:reserved: 00 00 00 00 00 00 00 00 00 00 FAT32:reserved: 00 FAT32:creation 10th: 0x00 FAT32:creation time: 0x0000 FAT32:creation date: 0x0000 FAT32:last accessed: 0x0000 FAT32:hi word start cluster: 0x0000 Time: 0x0000 (00:00:00) (hms) Date: 0x0000 (1980/00/00) (ymd) Starting Cluster: 0x0000 (0x00000000) <--- should be 0x0002 or higher. File Size: 0 FAT Root Entry (non LFN) (0x00000080) Name: ········ Extension: ··· Attribute: 0x00 FAT12:reserved: 00 00 00 00 00 00 00 00 00 00 FAT32:reserved: 00 FAT32:creation 10th: 0x00 FAT32:creation time: 0x0000 FAT32:creation date: 0x0000 FAT32:last accessed: 0x0000 FAT32:hi word start cluster: 0x0000 Time: 0x0000 (00:00:00) (hms) Date: 0x0000 (1980/00/00) (ymd) Starting Cluster: 0x0000 (0x00000000) <--- should be 0x0002 or higher. File Size: 0 FAT32 Info Block (0x00000000) sig: 0x209000EB (' ···') [1] <--- should be 0x41615252. reserved: 00000004 20 20 20 20 20 20 20 00-02 40 B2 05 02 00 00 00 .........@...... 00000014 00 F8 00 00 3F 00 FF 00-00 20 00 00 00 10 A5 03 ....?........... 00000024 27 1D 00 00 00 00 00 00-02 00 00 00 01 00 06 00 '............... 00000034 00 00 00 00 00 00 00 00-00 00 00 00 80 00 29 01 ..............). 00000044 02 03 04 20 20 20 20 20-20 20 20 20 20 20 46 41 ..............FA 00000054 54 33 32 20 20 20 00 00-00 00 00 00 00 00 00 00 T32.............
The most confusing part is that the FAT32 signature - the magic number is always supposed to be 0x41615252. Google that. You'll see. It's a hardcoded signature but maybe I've got the wrong offset and at that point all bets are off.
So do I have that? I can search a binary file for Hex values with a combo of xxd and grep. Note the byte swap:
xxd another.img | grep "6141" 00000200: 5252 6141 0000 0000 0000 0000 0000 0000 RRaA............ 00000e00: 5252 6141 0000 0000 0000 0000 0000 0000 RRaA............
Just before this is 55 AA which is the last two bytes of the 64 byte partition table.
Now do I have two FAT32 info blocks and three Root Entries? I'm lost. I'll update this part as I learn more.
7zip all the things
Here's where it gets weird and it got so weird that both Pete Brown and I were like, WELL. THAT'S AMAZING.
On a whim I right-clicked the IMG file and opened it in 7zip and saw this.
See that directory there that's a nothing? A space? A something. It has no Short Name. It's an invalid entry but 7zip is cool with it. Let's go in. Watch the path and the \\. That's a path separator, nothing, and another path separator. That's not allowed or OK but again, 7zip is chill.
I dragged the files out and they're fine! The day is saved.
The moral? There are a few I can see.
Re-format the random SD cards you get from Amazon specifically on the device you're gonna use them.
FAT as a spec has a bunch of stuff that different "drivers" (Windows, VFAT, etc) may ignore or elide over or just not implement.
I've got 85% of the knowledge I need to spelunk something like this but that last 15% is a brick wall. I would need more patience and to read more about this.
Knowing how to do this is useful for any engineer. It's the equivalent of knowing how to drive a stick shift in an emergency even if you usually use Lyft.
I'm clearly not an expert but I do have a mental model that includes (but not limited to) bytes on the physical media, the file system itself, file tables, directory tables, partition tables, how they kinda work on Linux and Windows.
I clearly hit a wall as I know what I want to do but I'm not sure the next step.
There's a bad Directory Table Entry. I want to rename it and make sure it's complete and to spec.
7zip is amazing. Try it first for basically everything.
Ideally I'd be able to update this post with exactly what byte is wrong and how to fix it. Thanks to Ali, Pete, and Leandro for playing with me!
Your thoughts? (If you made it this far the truncated IMG of the 32 gig SD is here (500 megs) but you might have to pad it out with zeros to make some tools like it.
Oh, and listen to https://hanselminutes.com/ as the interview was great and it's coming soon!
Sponsor: Have you tried developing in Rider yet? This fast and feature-rich cross-platform IDE improves your code for .NET, ASP.NET, .NET Core, Xamarin, and Unity applications on Windows, Mac, and Linux.
© 2019 Scott Hanselman. All rights reserved.
Tumblr media Tumblr media Tumblr media Tumblr media Tumblr media
      CSI: The case of the missing WAV audio files on the FAT32 SD Card published first on https://deskbysnafu.tumblr.com/
0 notes
philipholt · 4 years
Text
CSI: The case of the missing WAV audio files on the FAT32 SD Card
Buckle up kids, as this is a tale. As you may know, I have a lovely podcast at https://hanselminutes.com. You should listen.
Recently through an number of super cool random events I got the opportunity to interview actor Chris Conner who plays Poe on Altered Carbon. I'm a big fan of the show but especially Chris. You should watch the show because Poe is a joy and Chris owns every scene, and that's with a VERY strong cast.
I usually do my interviews remotely for the podcast but I wanted to meet Chris and hang out in person so I used my local podcasting rig which consists of a Zoom H6 recorder.
I have two Shure XLR mics, a Mic stand, and the Zoom. The Zoom H6 is a very well though of workhorse and I've used it many times before when recording shows. It's not rocket surgery but one should always test their things.
I didn't want to take any chances to I picked up a 5 pack of 32GIG high quality SD Cards. I put a new one in the Zoom, the Zoom immediately recognized the SD Card so I did a local recording right there and played it back. Sounds good. I played it back locally on the Zoom and I could hear the recording from the Zoom's local speaker. It's recording the file in stereo, one side for each mic. Remember this for later.
I went early to the meet and set up the whole recording setup. I hooked up a local monitor and tested again. Records and plays back locally. Cool. Chris shows up, we recorded a fantastic show, he's engaged and we're now besties and we go to Chipotle, talk shop, Sci-fi, acting, AIs, etc. Just a killer afternoon all around.
I head home and pull out the SD Card and put it into the PC and I see this. I almost vomit. I get lightheaded.
I've been recording the show for over 730 episodes over 14 years and I've never lost a show. I do my homework - as should you. I'm reeling. Ok, breathe. Let's work the problem.
Right click the drive, check properties. Breathe. This is a 32 gig drive, but Windows sees that it's got 329 MB used. 300ish megs is the size of a 30 minute long two channel WAV file. I know this because I've looked at 300 meg files for the last several hundred shows. Just like you might know roughly the size of a JPEG your camera makes. It's a thing you know.
Command line time. List the root directory. Empty. Check it again but "show all files," weird, there's a Mac folder there but maybe the SD Card was preformatted on a Mac.
Interesting Plot Point - I didn't format the SD card. I use it as it came out of the packaging from Amazon. It came preformatted and I accepted it. I tested it and it worked but I didn't "install my own carpet." I moved in to the house as-is.
What about a little "show me all folders from here down" action? Same as I saw in Windows Explorer. The root folder has another subfolder which is itself. It's folder "Inception" with no Kick!
G:\>dir /a Volume in drive G has no label. Volume Serial Number is 0403-0201 Directory of G:\ 03/12/2020 12:29 PM <DIR> 03/13/2020 12:44 PM <DIR> System Volume Information 0 File(s) 0 bytes 2 Dir(s) 30,954,225,664 bytes free G:\>dir /s Volume in drive G has no label. Volume Serial Number is 0403-0201 Directory of G:\ 03/12/2020 12:29 PM <DIR> 0 File(s) 0 bytes Directory of G:\ 03/12/2020 12:29 PM <DIR> 0 File(s) 0 bytes IT GOES FOREVER
Ok, the drive thinks there's data but I can't see it. I put the SD card back in the Zoom and try to play it back.
The Zoom can see folders and files AND the interview itself. And the Zoom can play it back. The Zoom is an embedded device with an implementation of the FAT32 file system and it can read it, but Windows can't. Can Linux? Can a Mac?
Short answer. No.
Hacky Note: Since the Zoom can see and play the file and it has a headphone/monitor jack, I could always plug in an analog 1/8" headphone cable to a 1/4" input on my Peavy PV6 Mixer and rescue the audio with some analog quality loss. Why don't I use the USB Audio out feature of the Zoom H6 and play the file back over a digital cable, you ask? Because the Zoom audio player doesn't support that. It supports three modes - SD Card Reader (which is a pass through to Windows and shows me the recursive directories and no files), an Audio pass-through which lets the Zoom look like an audio device to Windows but doesn't show the SD card as a drive or allow the SD Card to be played back over the digital interface, or its main mode where it's recording locally.
It's Forensics Time, Kids.
We have an 32 SD Card - a disk drive as it were - that is standard FAT32 formatted, that has 300-400 megs of a two-channel (Chris and I had two mics) WAV file that was recorded locally by the Zoom H6  audio reorder and I don't want too lose it or mess it up.
I need to take a byte for byte image of what's on the SD Card so I can poke and it and "virtually" mess with with it, change it, fix it, try again, without changing the physical.
"dd" is a command-line utility with a rich and storied history going back 45 years. Even though it means "Data Definition" it'll always be "disk drive" I my head.
How to clone a USB Drive or SD Card to an IMG file on Windows
I have a copy of dd for Windows which lets me get a byte for byte stream/file that represents this SD Card. For example I could get an entire USD device:
dd if=\\?\Device\Harddisk1\Partition0 of=c:\temp\usb2.img bs=1M --size --progress
I need to know the Harddisk number and Partition number as you can see above. I usually use diskpart for this.
>diskpart Microsoft DiskPart version 10.0.19041.1 Copyright (C) Microsoft Corporation. On computer: IRONHEART DISKPART> list disk Disk ### Status Size Free Dyn Gpt -------- ------------- ------- ------- --- --- Disk 0 Online 476 GB 0 B * Disk 1 Online 1863 GB 0 B * Disk 2 Online 3725 GB 0 B Disk 3 Online 2794 GB 0 B * Disk 8 Online 29 GB 3072 KB DISKPART> select disk 8 Disk 8 is now the selected disk. DISKPART> list part Partition ### Type Size Offset ------------- ---------------- ------- ------- Partition 1 Primary 29 GB 4096 KB
Looks like it's Disk 8 Partition 1 on my system. Let's get it all before I panic.
dd if=\\?\Device\Harddisk8\Partition1 of=c:\temp\ZOMG.img bs=1M --size --progress
IF and OF are input file and output file, and I will do it for the whole size of the SD Card. It's likely overkill though as we'll see in a second.
This file ended up being totally massive and hard to work with. Remember I needed just the first 400ish megs? I'll chop of just that part.
dd if=ZOMG.img of=SmallerZOMG.img bs=1M count=400
What is this though? Remember it's an image of a File System. It just bytes in a file. It's not a WAV file or a THIS file or a THAT file. I mean, it is if we decide it is, but in fact, a way to think about it is that it's a mangled envelope that is dark when I peer inside it. We're gonna have to feel around and see if we can rebuild a sense of what the contents really are.
Importing Raw Bytes from an IMG into Audition or Audacity
Both Adobe Audition and Audacity are audio apps that have an "Import RAW Data" feature. However, I DO need to tell Audition how to interpret it. There's lots of WAV files out there. How many simples were there? 1 channel? 2 channel? 16 bit or 32 bit? Lots of questions.
Can I just import this 4 gig byte array of a file system and get something?
Looks like something. You can see that the first part there is likely the start of the partition table, file system headers, etc. before audio data shows up. Here's importing as 2 channel.
I can hear voices but they sound like chipmunks and aren't understandable. Something is "doubled." Sample rate? No, I double checked it.
Here's 1 channel raw data import even though I think it's two.
Now THIS is interesting. I can hear audio at normal speed of us talking (after the preamble) BUT it's only a syllable at a time, and then a quieter version of the same syllable repeats. I don't want to (read: can't really) reassemble a 30 min interview from syllables, right?
Remember when I said that the Zoom H6 records a two channel file with one channel per mic? Not really. It records ONE FILE PER CHANNEL. A whateverL.wav and a whateverR.wav. I totally forgot!
This "one channel" file above is actually the bytes as they were laid down on disk, right? It's actually two files written simultaneously, a few kilobytes at a time, L,R,L,R,L,R. And here I am telling my sound software to treat this "byte for byte file system dump" as one file. It's two that were made at the same time.
It's like the Brundlefly. How do I tease it apart? Well I can't treat the array as a raw file anymore, it's not. And I want (really don't have the energy yet) to write my own little app to effectively de-interlace this image. I also don't know if the segment size is perfectly reliable or if it varies as the Zoom recorded.
NOTE: Pete Brown has written about RIFF/WAV files from Sound Devices records having an incorrect FAT32 bit set. This isn't that, but it's in the same family and is worth noting if you ever have an issue with a Broadcast Wave File getting corrupted or looking encrypted.
Whole helping me work this issue, Pete Brown tweeted a hexdump of the Directory Table so you can see the Zoom0001, Zoom0002, etc directories there in the image.
Let me move into Ubuntu on my Windows machine running WSL. Here I can run fdisk and get some sense of what this Image of the bad SD Card is. Remember also that I hacked off the first 0-400 Megs but this IMG file thinks it's a 32gig drive, because it is. It's just that's been aggressively truncated.
$ fdisk -u -l SmallerZOMG.img Disk SmallerZOMG.img: 400 MiB, 419430400 bytes, 819200 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x00000000 Device Boot Start End Sectors Size Id Type SmallerZOMG.img1 8192 61157375 61149184 29.2G c W95 FAT32 (LBA)
Maybe I can "mount" this IMG? I make a folder on Ubuntu/WSL2 called ~/recovery. Yikes, ok there's nothing there. I can take the sector size 512 times the Start block of 8192 and use that as the offset.
sudo mount -o loop,offset=4194304 SmallerShit.img recover/ $ cd recover/ $ ll total 68 drwxr-xr-x 4 root root 32768 Dec 31 1969 ./
Ali Mosajjal thinks perhaps "they re-wrote the FAT32 structure definition and didn't use a standard library and made a mistake," and Leandro Pereria postulates "what could happen is that the LFN (long file name) checksum is invalid and they didn't bother filling in the 8.3 filename... so that complying implementations of VFAT tries to look at the fallback 8.3 name, it's all spaces and figures out "it's all padding, move along."
Ali suggested running dosfsck on the mounted image and you can see again that the files are there, but there's like 3 root entries? Note I've done a cat of /proc/mounts to see the loop that my img is mounted on so I can refer to it in the dosfsck command.
$ sudo dosfsck -w -r -l -a -v -t /dev/loop3 fsck.fat 4.1 (2017-01-24) Checking we can access the last sector of the filesystem Boot sector contents: System ID " " Media byte 0xf8 (hard disk) 512 bytes per logical sector 32768 bytes per cluster 1458 reserved sectors First FAT starts at byte 746496 (sector 1458) 2 FATs, 32 bit entries 3821056 bytes per FAT (= 7463 sectors) Root directory start at cluster 2 (arbitrary size) Data area starts at byte 8388608 (sector 16384) 955200 data clusters (31299993600 bytes) 63 sectors/track, 255 heads 8192 hidden sectors 61149184 sectors total Checking file / Checking file / Checking file / Checking file /System Volume Information (SYSTEM~1) Checking file /. Checking file /.. Checking file /ZOOM0001 Checking file /ZOOM0002 Checking file /ZOOM0003 Checking file /ZOOM0001/. Checking file /ZOOM0001/.. Checking file /ZOOM0001/ZOOM0001.hprj (ZOOM00~1.HPR) Checking file /ZOOM0001/ZOOM0001_LR.WAV (ZOOM00~1.WAV) Checking file /ZOOM0002/. Checking file /ZOOM0002/.. Checking file /ZOOM0002/ZOOM0002.hprj (ZOOM00~1.HPR) Checking file /ZOOM0002/ZOOM0002_Tr1.WAV (ZOOM00~1.WAV) Checking file /ZOOM0002/ZOOM0002_Tr2.WAV (ZOOM00~2.WAV) Checking file /ZOOM0003/. Checking file /ZOOM0003/.. Checking file /ZOOM0003/ZOOM0003.hprj (ZOOM00~1.HPR) Checking file /ZOOM0003/ZOOM0003_Tr1.WAV (ZOOM00~1.WAV) Checking file /ZOOM0003/ZOOM0003_Tr2.WAV (ZOOM00~2.WAV) Checking file /System Volume Information/. Checking file /System Volume Information/.. Checking file /System Volume Information/WPSettings.dat (WPSETT~1.DAT) Checking file /System Volume Information/ClientRecoveryPasswordRotation (CLIENT~1) Checking file /System Volume Information/IndexerVolumeGuid (INDEXE~1) Checking file /System Volume Information/AadRecoveryPasswordDelete (AADREC~1) Checking file /System Volume Information/ClientRecoveryPasswordRotation/. Checking file /System Volume Information/ClientRecoveryPasswordRotation/.. Checking file /System Volume Information/AadRecoveryPasswordDelete/. Checking file /System Volume Information/AadRecoveryPasswordDelete/.. Checking for bad clusters.
We can see  them, but can't get at them with the vfat file system driver on Linux or with Windows.
The DUMP.exe util as part of mtools for Windows is amazing but I'm unable to figure out what is wrong in the FAT32 file table. I can run minfo on the Linux command land telling it to skip 8192 sectors in with the @@offset modifier:
$ minfo -i ZOMG.img@@8192S device information: =================== filename="ZOMG.img" sectors per track: 63 heads: 255 cylinders: 3807 mformat command line: mformat -T 61149184 -i ZOMG.img@@8192S -h 255 -s 63 -H 8192 :: bootsector information ====================== banner:" " sector size: 512 bytes cluster size: 64 sectors reserved (boot) sectors: 1458 fats: 2 max available root directory slots: 0 small size: 0 sectors media descriptor byte: 0xf8 sectors per fat: 0 sectors per track: 63 heads: 255 hidden sectors: 8192 big size: 61149184 sectors physical drive id: 0x80 reserved=0x0 dos4=0x29 serial number: 04030201 disk label=" " disk type="FAT32 " Big fatlen=7463 Extended flags=0x0000 FS version=0x0000 rootCluster=2 infoSector location=1 backup boot sector=6 Infosector: signature=0x41615252 free clusters=944648 last allocated cluster=10551
Ok, now we've found yet ANOTHER way to mount this corrupted file system. With mtools we'll use mdir to list the root directory. Note there is something wrong enough that I have to set mtools_skip_check=1 to ~/.mtoolsrc and continue.
$ mdir -i ZOMG.img@@8192S :: Total number of sectors (61149184) not a multiple of sectors per track (63)! Add mtools_skip_check=1 to your .mtoolsrc file to skip this test $ pico ~/.mtoolsrc $ mdir -i ZOMG.img@@8192S :: Volume in drive : is Volume Serial Number is 0403-0201 Directory for ::/ <DIR> 2020-03-12 12:29 1 file 0 bytes 30 954 225 664 bytes free
Same result. I can run mdu and see just a few folders. Note the ZOOMxxxx ones are missing here
$ mdu -i ZOMG.img@@8192S :: ::/System Volume Information/ClientRecoveryPasswordRotation 1 ::/System Volume Information/AadRecoveryPasswordDelete 1 ::/System Volume Information 5 ::/ 6
Now, ideally I want to achieve two things here.
Know WHY it's broken and exactly WHAT is wrong.
There's a nameless root directory here and I lack the patience and skill to manually hexdump and patch it.
Be able to copy the files out "normally" by mounting the IMG and, well, copying them out.
UPDATE #1 - I'm back after a few minutes of thinking again.
If I use mmls from Sleuthkit, I can see this.
$ mmls HolyShit.img DOS Partition Table Offset Sector: 0 Units are in 512-byte sectors Slot Start End Length Description 000: Meta 0000000000 0000000000 0000000001 Primary Table (#0) 001: ------- 0000000000 0000008191 0000008192 Unallocated 002: 000:000 0000008192 0061157375 0061149184 Win95 FAT32 (0x0c)
If I do the 512*8192 offset again and visualize the FAT32 table in Hexdump/xxd like this:
xxd -seek 4194304 ZOMG.img | more 00400000: eb00 9020 2020 2020 2020 2000 0240 b205 ... ..@.. 00400010: 0200 0000 00f8 0000 3f00 ff00 0020 0000 ........?.... .. 00400020: 0010 a503 271d 0000 0000 0000 0200 0000 ....'........... 00400030: 0100 0600 0000 0000 0000 0000 0000 0000 ................ 00400040: 8000 2901 0203 0420 2020 2020 2020 2020 ..).... 00400050: 2020 4641 5433 3220 2020 0000 0000 0000 FAT32 ...... 00400060: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00400070: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00400080: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00400090: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 004000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 004000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 004000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
I can see I seek'ed to the right spot, as the string FAT32 is just hanging out. Maybe I can clip out this table and visualize it in a better graphical tool.
I could grab a reasonable (read: arbitrary) chunk from this offset and put it in a very small manageable file:
dd if=ZOMG.img ibs=1 skip=4194304 count=64000 > another.img
And then load it in dump.exe on Windows which is really a heck of a tool. It seems to be thinking thinking there's multiple FAT Root Entries (which might be why I'm seeing this weird ghost root). Note the "should be" parts as well.
FAT Root Entry (non LFN) (0x00000000) Name: ··· Extension: Attribute: 0x00 FAT12:reserved: 02 40 B2 05 02 00 00 00 00 F8 FAT32:reserved: 02 FAT32:creation 10th: 0x40 FAT32:creation time: 0x05B2 FAT32:creation date: 0x0002 FAT32:last accessed: 0x0000 FAT32:hi word start cluster: 0xF800 Time: 0x0000 (00:00:00) (hms) Date: 0x003F (1980/01/31) (ymd) Starting Cluster: 0x00FF (0xF80000FF) File Size: 8192 FAT Root Entry (non LFN) (0x00000020) Name: ····'··· Extension: ··· Attribute: 0x00 FAT12:reserved: 02 00 00 00 01 00 06 00 00 00 FAT32:reserved: 02 FAT32:creation 10th: 0x00 FAT32:creation time: 0x0000 FAT32:creation date: 0x0001 FAT32:last accessed: 0x0006 FAT32:hi word start cluster: 0x0000 Time: 0x0000 (00:00:00) (hms) Date: 0x0000 (1980/00/00) (ymd) Starting Cluster: 0x0000 (0x00000000) <--- should be 0x0002 or higher. File Size: 0 FAT Root Entry (non LFN) (0x00000040) Name: ··)···· Extension: Attribute: 0x20 Archive FAT12:reserved: 20 20 20 20 20 20 46 41 54 33 FAT32:reserved: 20 FAT32:creation 10th: 0x20 FAT32:creation time: 0x2020 FAT32:creation date: 0x2020 FAT32:last accessed: 0x4146 FAT32:hi word start cluster: 0x3354 Time: 0x2032 (04:01:18) (hms) Date: 0x2020 (1996/01/00) (ymd) Starting Cluster: 0x0000 (0x33540000) File Size: 0 FAT Root Entry (non LFN) (0x00000060) Name: ········ Extension: ··· Attribute: 0x00 FAT12:reserved: 00 00 00 00 00 00 00 00 00 00 FAT32:reserved: 00 FAT32:creation 10th: 0x00 FAT32:creation time: 0x0000 FAT32:creation date: 0x0000 FAT32:last accessed: 0x0000 FAT32:hi word start cluster: 0x0000 Time: 0x0000 (00:00:00) (hms) Date: 0x0000 (1980/00/00) (ymd) Starting Cluster: 0x0000 (0x00000000) <--- should be 0x0002 or higher. File Size: 0 FAT Root Entry (non LFN) (0x00000080) Name: ········ Extension: ··· Attribute: 0x00 FAT12:reserved: 00 00 00 00 00 00 00 00 00 00 FAT32:reserved: 00 FAT32:creation 10th: 0x00 FAT32:creation time: 0x0000 FAT32:creation date: 0x0000 FAT32:last accessed: 0x0000 FAT32:hi word start cluster: 0x0000 Time: 0x0000 (00:00:00) (hms) Date: 0x0000 (1980/00/00) (ymd) Starting Cluster: 0x0000 (0x00000000) <--- should be 0x0002 or higher. File Size: 0 FAT32 Info Block (0x00000000) sig: 0x209000EB (' ···') [1] <--- should be 0x41615252. reserved: 00000004 20 20 20 20 20 20 20 00-02 40 B2 05 02 00 00 00 .........@...... 00000014 00 F8 00 00 3F 00 FF 00-00 20 00 00 00 10 A5 03 ....?........... 00000024 27 1D 00 00 00 00 00 00-02 00 00 00 01 00 06 00 '............... 00000034 00 00 00 00 00 00 00 00-00 00 00 00 80 00 29 01 ..............). 00000044 02 03 04 20 20 20 20 20-20 20 20 20 20 20 46 41 ..............FA 00000054 54 33 32 20 20 20 00 00-00 00 00 00 00 00 00 00 T32.............
The most confusing part is that the FAT32 signature - the magic number is always supposed to be 0x41615252. Google that. You'll see. It's a hardcoded signature but maybe I've got the wrong offset and at that point all bets are off.
So do I have that? I can search a binary file for Hex values with a combo of xxd and grep. Note the byte swap:
xxd another.img | grep "6141" 00000200: 5252 6141 0000 0000 0000 0000 0000 0000 RRaA............ 00000e00: 5252 6141 0000 0000 0000 0000 0000 0000 RRaA............
Just before this is 55 AA which is the last two bytes of the 64 byte partition table. mm
Now do I have two FAT32 info blocks and three Root Entries? I'm lost. I want to dump the directory entries.
What does fsstat say about the Root Directory?
File System Layout (in sectors) Total Range: 0 - 61149183 * Reserved: 0 - 1457 ** Boot Sector: 0 ** FS Info Sector: 1 ** Backup Boot Sector: 6 * FAT 0: 1458 - 8920 * FAT 1: 8921 - 16383 * Data Area: 16384 - 61149183 ** Cluster Area: 16384 - 61149183 *** Root Directory: 16384 - 16447
I'll update this part as I learn more. I'm exhausted. Someone will likely read this and be like "you dork, seek HERE" and there's the byte that's wrong in the file system. That LFN (long file name) has no short one, etc" and then I'll know.
UPDATE #2:
I skyped with Ali and we think we know what's up. He suggested I format the SD Card, record the same 3 shows (two test WAVs and one actual one) and then make an image of the GOOD disk to remove variables. Smart guy!
We then took the first 12 megs or so of the GOOD.img and the BAD.img and piped them through xxd into HEX, then used Visual Studio Code to diff them.
We can now visualize on the left what a good directory structure looks like and the right what a bad one looks like. Seems like I do have two recursive root directories with a space for the name.
Now if we wanted we could manually rewrite a complete new directory entry and assign our orphaned files to it.
That's what I would do if I was hired to recover data.
7zip all the things
Here's where it gets weird and it got so weird that both Pete Brown and I were like, WELL. THAT'S AMAZING.
On a whim I right-clicked the IMG file and opened it in 7zip and saw this.
See that directory there that's a nothing? A space? A something. It has no Short Name. It's an invalid entry but 7zip is cool with it. Let's go in. Watch the path and the \\. That's a path separator, nothing, and another path separator. That's not allowed or OK but again, 7zip is chill.
I dragged the files out and they're fine! The day is saved.
The moral? There are a few I can see.
Re-format the random SD cards you get from Amazon specifically on the device you're gonna use them.
FAT as a spec has a bunch of stuff that different "drivers" (Windows, VFAT, etc) may ignore or elide over or just not implement.
I've got 85% of the knowledge I need to spelunk something like this but that last 15% is a brick wall. I would need more patience and to read more about this.
Knowing how to do this is useful for any engineer. It's the equivalent of knowing how to drive a stick shift in an emergency even if you usually use Lyft.
I'm clearly not an expert but I do have a mental model that includes (but not limited to) bytes on the physical media, the file system itself, file tables, directory tables, partition tables, how they kinda work on Linux and Windows.
I clearly hit a wall as I know what I want to do but I'm not sure the next step.
There's a bad Directory Table Entry. I want to rename it and make sure it's complete and to spec.
7zip is amazing. Try it first for basically everything.
Ideally I'd be able to update this post with exactly what byte is wrong and how to fix it. Thanks to Ali, Pete, and Leandro for playing with me!
Your thoughts? (If you made it this far the truncated IMG of the 32 gig SD is here (500 megs) but you might have to pad it out with zeros to make some tools like it.
Oh, and listen to https://hanselminutes.com/ as the interview was great and it's coming soon!
Sponsor: Have you tried developing in Rider yet? This fast and feature-rich cross-platform IDE improves your code for .NET, ASP.NET, .NET Core, Xamarin, and Unity applications on Windows, Mac, and Linux.
© 2019 Scott Hanselman. All rights reserved.
Tumblr media Tumblr media Tumblr media Tumblr media Tumblr media
      CSI: The case of the missing WAV audio files on the FAT32 SD Card published first on http://7elementswd.tumblr.com/
0 notes
just4programmers · 6 years
Text
Optimizing ASP.NET Core Docker Image sizes
There is a great post from Steve Laster in 2016 about optimizing ASP.NET Docker Image sizes. Since then Docker has added multi-stage build files so you can do more in one Dockerfile...which feels like one step even though it's not. Containers are about easy and reliable deployment, and they're also about density. You want to use as little memory as possible, sure, but it also is nice to make them as small as possible so you're not spending time moving them around the network. The size of the image file can also affect startup time for the container. Plus it's just tidy.
I've been building a little 6 node Raspberry Pi (ARM) Kubenetes Cluster on my desk - like you do - this week, and I noticed that my image sizes were a little larger than I'd like. This is a bigger issue because it's a relatively low-powered system, but again, why carry around x unnecessary megabytes if you don't have to?
Alex Ellis has a great blog on building .NET Core apps for Raspberry Pi along with a YouTube video. In his video and blog he builds a "Console.WriteLine()" console app, which is great for OpenFaas (open source serverless platform) but I wanted to also have ASP.NET Core apps on my Raspberry Pi k8s cluster. He included this as a "challenge" in his blog, so challenge accepted! Thanks for all your help and support, Alex!
ASP.NET Core on Docker (on ARM)
First I make a basic ASP.NET Core app. I could do a Web API, but this time I'll do an MVC one with Razor Pages. To be clear, they are the same thing just with different starting points. I can always add pages or add JSON to either, later.
I start with "dotnet new mvc" (or dotnet new razor, etc). I'm going to be running this in Docker, managed by Kuberenetes, and while I can always change the WebHost in Program.cs to change how the Kestrel web server starts up like this:
WebHost.CreateDefaultBuilder(args) .UseUrls(http://*:5000;http://localhost:5001;https://hostname:5002)
For Docker use cases it's easier to change the listening URL with an Environment Variable. Sure, it could be 80, but I like 5000. I'll set the ASPNETCORE_URLS environment variable to http://+:5000 when I make the Dockerfile.
Optimized MultiStage Dockerfile for ASP.NET
There's a number of "right" ways to do this, so you'll want to think about your scenarios. You'll see below that I'm using ARM (because Raspberry Pi) so if you see errors running your container like "qemu: Unsupported syscall: 345" then you're trying to run an ARM image on x86/x64. I'm going to be building an ARM container from Windows but I can't run it here. I have to push it to a container registry and then tell my Raspberry Pi cluster to pull it down and THEN it'll run, over there.
Here's what I have so far. NOTE there are some things commented out, so be conscious. This is/was a learning exercise for me. Don't you copy/paste unless you know what's up! And if there's a mistake, here's a GitHub Gist of my Dockerfile for you to change and improve.
It's important to understand that .NET Core has an SDK with build tools and development kits and compilers and stuff, and then it has a runtime. The runtime doesn't have the "make an app" stuff, it only has the "run an app stuff." There is not currently an SDK for ARM so that's a limitation that we are (somewhat elegantly) working around with the multistage build file. But, even if there WAS an SDK for ARM, we'd still want to use a Dockerfile like this because it's more efficient with space and makes a smaller image.
Let's break this down. There are two stages. The first FROM is the SDK image that builds the code. We're doing the build inside Docker - which is lovely, and  great reliable way to do builds.
PRO TIP: Docker is smart about making intermediate images and doing the least work, but it's useful if we (the authors) do the right thing as well to help it out.
For example, see where we COPY the .csproj over and then do a "dotnet restore"? Often you'll see folks do a "COPY . ." and then do a restore. That doesn't allow Docker to detect what's changed and you'll end up paying for the restore on EVERY BUILD.
By making this two steps - copy the project, restore, copy the code, this means your "dotnet restore" intermediate step will be cached by Docker and things will be WAY faster.
After you build, you'll do a publish. If you know the destination like I do (linux-arm) you can do a RID (runtime id) publish that is self-contained with -r linux-arm (or debian, or whatever) and you'll get a complete self-contained version of your app.
Otherwise, you can just publish your app's code and use a .NET Core runtime image to run it. Since I'm using a complete self-contained build for this image, it would be overkill to ALSO include the .NET runtime. If you look at the Docker hub for Microsoft/dotnet You'll see images called "deps" for "dependencies." Those are images that sit on top of debian that include the things .NET needs to run - but not .NET itself.
The stack of images looks generally like this (for example)
FROM debian:stretch
FROM microsoft/dotnet:2.0-runtime-deps
FROM microsoft/dotnet:2.0-runtime
So you have your base image, your dependencies, and your .NET runtime. The SDK image would include even more stuff since it needs to build code. Again, that's why we use that for the "as builder" image and then copy out the results of the compile and put them in another runtime image. You get the best of all worlds.
FROM microsoft/dotnet:2.0-sdk as builder RUN mkdir -p /root/src/app/aspnetcoreapp WORKDIR /root/src/app/aspnetcoreapp #copy just the project file over # this prevents additional extraneous restores # and allows us to re-use the intermediate layer # This only happens again if we change the csproj. # This means WAY faster builds! COPY aspnetcoreapp.csproj . #Because we have a custom nuget.config, copy it in COPY nuget.config . RUN dotnet restore ./aspnetcoreapp.csproj COPY . . RUN dotnet publish -c release -o published -r linux-arm #Smaller - Best for apps with self-contained .NETs, as it doesn't include the runtime # It has the *dependencies* to run .NET Apps. The .NET runtime image sits on this FROM microsoft/dotnet:2.0.0-runtime-deps-stretch-arm32v7 #Bigger - Best for apps .NETs that aren't self-contained. #FROM microsoft/dotnet:2.0.0-runtime-stretch-arm32v7 # These are the non-ARM images. #FROM microsoft/dotnet:2.0.0-runtime-deps #FROM microsoft/dotnet:2.0.0-runtime WORKDIR /root/ COPY --from=builder /root/src/app/aspnetcoreapp/published . ENV ASPNETCORE_URLS=http://+:5000 EXPOSE 5000/tcp # This runs your app with the dotnet exe included with the runtime or SDK #CMD ["dotnet", "./aspnetcoreapp.dll"] # This runs your self-contained .NET Core app. You built with -r to get this CMD ["./aspnetcoreapp"]
Notice also that I have a custom nuget.config, so if you do also you'll need to make sure that's available at build time for dotnet restore to pick up all packages.
I've included by commented out a bunch of the FROMs in the second stage. I'm using just the ARM one, but I wanted you to see the others.
Once we have the code we build copied into our runtime image, we set our environment variable so our all listens on port 5000 internally (remember that from above?) Then we run our app. Notice that you can run it with "dotnet foo.dll" if you have the runtime, but if you are like me and using a self-contained build, then you'll just run "foo."
To sum up:
Build with FROM microsoft/dotnet:2.0-sdk as builder
Copy the results out to a runtime
Use the right runtime FROM for you
Right CPU architecture?
Using the .NET Runtime (typical) or using a self-contained build (less so)
Listening on the right port (if a web app)?
Running your app successfully and correctly?
Do you have a .dockerignore? Super important for .NET Builds, as you don't' want to copy over /obj, /bin, etc, but you do want /published. obj/ bin/ !published/
Optimizing a little more
There are a few pre-release "Tree Trimming" tools that can look at your app and remove code and binaries that you are not calling. I included Microsoft.Packaging.Tools.Trimming as well to try it out and get even more unused code out of my final image by just adding a package to my project.
Step 8/14 : RUN dotnet publish -c release -o published -r linux-arm /p:LinkDuringPublish=true ---> Running in 39404479945f Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core Copyright (C) Microsoft Corporation. All rights reserved. Trimmed 152 out of 347 files for a savings of 20.54 MB Final app size is 33.56 MB aspnetcoreapp -> /root/src/app/aspnetcoreapp/bin/release/netcoreapp2.0/linux-arm/aspnetcoreapp.dll Trimmed 152 out of 347 files for a savings of 20.54 MB Final app size is 33.56 MB
If you run docker history on your final image you can see exactly where the size comes from. If/when Microsoft switches from a Debian base image to an Alpine one, this should get even smaller.
C:\Users\scott\Desktop\k8s for pi\aspnetcoreapp>docker history c60 IMAGE CREATED CREATED BY SIZE COMMENT c6094ca46c3b 3 minutes ago /bin/sh -c #(nop) CMD ["dotnet" "./aspnet... 0B b7dfcf137587 3 minutes ago /bin/sh -c #(nop) EXPOSE 5000/tcp 0B a5ba51b91d9d 3 minutes ago /bin/sh -c #(nop) ENV ASPNETCORE_URLS=htt... 0B 8742269735bc 3 minutes ago /bin/sh -c #(nop) COPY dir:cc64bd3b9bacaeb... 56.5MB 28c008e38973 3 minutes ago /bin/sh -c #(nop) WORKDIR /root/ 0B 4bafd6e2811a 4 hours ago /bin/sh -c apt-get update && apt-get i... 45.4MB <missing> 3 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B <missing> 3 weeks ago /bin/sh -c #(nop) ADD file:8b7cf813a113aa2... 85.7MB
Here is the evolution of my Dockerfile as I made changes and the final result got smaller and smaller. Looks like 45 megs trimmed with a little work or about 20% smaller.
C:\Users\scott\Desktop\k8s for pi\aspnetcoreapp>docker images | find /i "aspnetcoreapp" shanselman/aspnetcoreapp 0.5 c6094ca46c3b About a minute ago 188MB shanselman/aspnetcoreapp 0.4 083bfbdc4e01 12 minutes ago 196MB shanselman/aspnetcoreapp 0.3 fa053b4ee2b4 About an hour ago 199MB shanselman/aspnetcoreapp 0.2 ba73f14e29aa 4 hours ago 207MB shanselman/aspnetcoreapp 0.1 cac2f0e3826c 3 hours ago 233MB
Later I'll do a blog post where I put this standard ASP.NET Core web app into Kubernetes using this YAML description and scale it out on the Raspberry Pi. I'm learning a lot! Thanks to Alex Ellis and Glenn Condron and Jessie Frazelle for their time!
Sponsor: Create powerful Web applications to manage each step of a document’s life cycle with DocuVieware HTML5 Viewer and Document Management Kit. Check our demos to acquire, scan, edit, annotate 100+ formats, and customize your UI!
© 2017 Scott Hanselman. All rights reserved.
Tumblr media Tumblr media Tumblr media Tumblr media Tumblr media
0 notes