Sfoglia il codice sorgente

Add weebo from https://github.com/bettse/weebo

git-subtree-dir: weebo
git-subtree-mainline: 8291f15f7fe54084f5d9aef037b81695183ff18d
git-subtree-split: 6aef3e221bf963dcbd677ea634f404414223de59
Willy-JL 8 mesi fa
parent
commit
dff3a13359
51 ha cambiato i file con 3127 aggiunte e 0 eliminazioni
  1. 6 0
      weebo/.catalog/README.md
  2. 8 0
      weebo/.catalog/changelog.md
  3. BIN
      weebo/.catalog/screenshots/emulate.png
  4. BIN
      weebo/.catalog/screenshots/info.png
  5. BIN
      weebo/.catalog/screenshots/menu.png
  6. 246 0
      weebo/.clang-format
  7. 1 0
      weebo/.gitattributes
  8. 41 0
      weebo/.github/workflows/build.yml
  9. 4 0
      weebo/.gitignore
  10. 1 0
      weebo/.gitsubtree
  11. 8 0
      weebo/README.md
  12. 2 0
      weebo/acknowledgements.h
  13. 21 0
      weebo/application.fam
  14. 542 0
      weebo/assets/figure_ids.nfc
  15. 3 0
      weebo/demo.mp4
  16. 0 0
      weebo/images/.gitkeep
  17. BIN
      weebo/images/DolphinNice_96x59.png
  18. BIN
      weebo/images/Nfc_10px.png
  19. 21 0
      weebo/lib/amiitool/LICENSE
  20. 29 0
      weebo/lib/amiitool/README.md
  21. 181 0
      weebo/lib/amiitool/amiibo.c
  22. 29 0
      weebo/lib/amiitool/amiibo.h
  23. 78 0
      weebo/lib/amiitool/drbg.c
  24. 33 0
      weebo/lib/amiitool/drbg.h
  25. 58 0
      weebo/lib/amiitool/keygen.c
  26. 34 0
      weebo/lib/amiitool/keygen.h
  27. 121 0
      weebo/lib/amiitool/portable_endian.h
  28. 27 0
      weebo/lib/amiitool/util.c
  29. 17 0
      weebo/lib/amiitool/util.h
  30. 21 0
      weebo/lib/amiitool/version.c
  31. 16 0
      weebo/lib/amiitool/version.h
  32. 30 0
      weebo/scenes/weebo_scene.c
  33. 29 0
      weebo/scenes/weebo_scene.h
  34. 45 0
      weebo/scenes/weebo_scene_acknowledgements.c
  35. 11 0
      weebo/scenes/weebo_scene_config.h
  36. 101 0
      weebo/scenes/weebo_scene_emulate.c
  37. 25 0
      weebo/scenes/weebo_scene_file_select.c
  38. 48 0
      weebo/scenes/weebo_scene_info.c
  39. 40 0
      weebo/scenes/weebo_scene_keys_missing.c
  40. 56 0
      weebo/scenes/weebo_scene_main_menu.c
  41. 97 0
      weebo/scenes/weebo_scene_save_name.c
  42. 42 0
      weebo/scenes/weebo_scene_save_success.c
  43. 72 0
      weebo/scenes/weebo_scene_saved_menu.c
  44. 242 0
      weebo/scenes/weebo_scene_write.c
  45. 51 0
      weebo/scenes/weebo_scene_write_card_success.c
  46. 505 0
      weebo/weebo.c
  47. 8 0
      weebo/weebo.h
  48. BIN
      weebo/weebo.png
  49. 44 0
      weebo/weebo_common.c
  50. 7 0
      weebo/weebo_common.h
  51. 126 0
      weebo/weebo_i.h

+ 6 - 0
weebo/.catalog/README.md

@@ -0,0 +1,6 @@
+# Weebo
+
+A Flipper Zero NTAG215 parser, writer, emulator, remixer, duplicator.
+
+**Put key_retail.bin into 'SD Card/apps_data/weebo/**
+

+ 8 - 0
weebo/.catalog/changelog.md

@@ -0,0 +1,8 @@
+## 0.2
+ - Info shows the ID of the figure
+ - More IDs added for Smash characters and many older figures
+ - Image assets fixed using fbt format_img
+ - FAM file expanded to include app creator / source information
+
+## 0.1
+ - Initial release

BIN
weebo/.catalog/screenshots/emulate.png


BIN
weebo/.catalog/screenshots/info.png


BIN
weebo/.catalog/screenshots/menu.png


+ 246 - 0
weebo/.clang-format

@@ -0,0 +1,246 @@
+---
+Language:        Cpp
+AccessModifierOffset: -4
+AlignAfterOpenBracket: AlwaysBreak
+AlignArrayOfStructures: None
+AlignConsecutiveAssignments:
+  Enabled:         false
+  AcrossEmptyLines: false
+  AcrossComments:  false
+  AlignCompound:   false
+  AlignFunctionPointers: false
+  PadOperators:    true
+AlignConsecutiveBitFields:
+  Enabled:         true
+  AcrossEmptyLines: true
+  AcrossComments:  true
+  AlignCompound:   false
+  AlignFunctionPointers: false
+  PadOperators:    true
+AlignConsecutiveDeclarations:
+  Enabled:         false
+  AcrossEmptyLines: false
+  AcrossComments:  false
+  AlignCompound:   false
+  AlignFunctionPointers: false
+  PadOperators:    true
+AlignConsecutiveMacros:
+  Enabled:         true
+  AcrossEmptyLines: false
+  AcrossComments:  true
+  AlignCompound:   true
+  AlignFunctionPointers: false
+  PadOperators:    true
+AlignConsecutiveShortCaseStatements:
+  Enabled:         false
+  AcrossEmptyLines: false
+  AcrossComments:  false
+  AlignCaseColons: false
+AlignEscapedNewlines: Left
+AlignOperands:   Align
+AlignTrailingComments:
+  Kind:            Never
+  OverEmptyLines:  0
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowBreakBeforeNoexceptSpecifier: Never
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortCompoundRequirementOnASingleLine: true
+AllowShortEnumsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: WithoutElse
+AllowShortLambdasOnASingleLine: All
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+AttributeMacros:
+  - __capability
+BinPackArguments: false
+BinPackParameters: false
+BitFieldColonSpacing: Both
+BraceWrapping:
+  AfterCaseLabel:  false
+  AfterClass:      false
+  AfterControlStatement: Never
+  AfterEnum:       false
+  AfterExternBlock: false
+  AfterFunction:   false
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     false
+  AfterUnion:      false
+  BeforeCatch:     false
+  BeforeElse:      false
+  BeforeLambdaBody: false
+  BeforeWhile:     false
+  IndentBraces:    false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakAdjacentStringLiterals: true
+BreakAfterAttributes: Leave
+BreakAfterJavaFieldAnnotations: false
+BreakArrays:     true
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: Always
+BreakBeforeBraces: Attach
+BreakBeforeInlineASMColon: OnlyMultiline
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializers: BeforeComma
+BreakInheritanceList: BeforeColon
+BreakStringLiterals: false
+ColumnLimit:     99
+CommentPragmas:  '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat:   false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: false
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+  - M_EACH
+IfMacros:
+  - KJ_IF_MAYBE
+IncludeBlocks:   Preserve
+IncludeCategories:
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
+    Priority:        3
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+IncludeIsMainRegex: '(Test)?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseBlocks: false
+IndentCaseLabels: false
+IndentExternBlock: AfterExternBlock
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentRequiresClause: false
+IndentWidth:     4
+IndentWrappedFunctionNames: true
+InsertBraces:    false
+InsertNewlineAtEOF: true
+InsertTrailingCommas: None
+IntegerLiteralSeparator:
+  Binary:          0
+  BinaryMinDigits: 0
+  Decimal:         0
+  DecimalMinDigits: 0
+  Hex:             0
+  HexMinDigits:    0
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+KeepEmptyLinesAtEOF: false
+LambdaBodyIndentation: Signature
+LineEnding:      DeriveLF
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 4
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+PackConstructorInitializers: BinPack
+PenaltyBreakAssignment: 10
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakScopeResolution: 500
+PenaltyBreakString: 10
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 100
+PenaltyIndentedWhitespace: 0
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Left
+PPIndentWidth:   -1
+QualifierAlignment: Leave
+ReferenceAlignment: Pointer
+ReflowComments:  false
+RemoveBracesLLVM: false
+RemoveParentheses: Leave
+RemoveSemicolon: true
+RequiresClausePosition: OwnLine
+RequiresExpressionIndentation: OuterScope
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SkipMacroDefinitionBody: false
+SortIncludes:    Never
+SortJavaStaticImport: Before
+SortUsingDeclarations: Never
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeJsonColon: false
+SpaceBeforeParens: Never
+SpaceBeforeParensOptions:
+  AfterControlStatements: false
+  AfterForeachMacros: false
+  AfterFunctionDefinitionName: false
+  AfterFunctionDeclarationName: false
+  AfterIfMacros:   false
+  AfterOverloadedOperator: false
+  AfterPlacementOperator: true
+  AfterRequiresInClause: false
+  AfterRequiresInExpression: false
+  BeforeNonEmptyParentheses: false
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceBeforeSquareBrackets: false
+SpaceInEmptyBlock: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  Never
+SpacesInContainerLiterals: false
+SpacesInLineCommentPrefix:
+  Minimum:         1
+  Maximum:         -1
+SpacesInParens:  Never
+SpacesInParensOptions:
+  InCStyleCasts:   false
+  InConditionalStatements: false
+  InEmptyParentheses: false
+  Other:           false
+SpacesInSquareBrackets: false
+Standard:        c++20
+StatementAttributeLikeMacros:
+  - Q_EMIT
+StatementMacros:
+  - Q_UNUSED
+  - QT_REQUIRE_VERSION
+TabWidth:        4
+UseTab:          Never
+VerilogBreakBetweenInstancePorts: true
+WhitespaceSensitiveMacros:
+  - STRINGIZE
+  - PP_STRINGIZE
+  - BOOST_PP_STRINGIZE
+  - NS_SWIFT_NAME
+  - CF_SWIFT_NAME
+...
+

+ 1 - 0
weebo/.gitattributes

@@ -0,0 +1 @@
+*.mp4 filter=lfs diff=lfs merge=lfs -text

+ 41 - 0
weebo/.github/workflows/build.yml

@@ -0,0 +1,41 @@
+name: "FAP: Build for multiple SDK sources"
+# This will build your app for dev and release channels on GitHub. 
+# It will also build your app every day to make sure it's up to date with the latest SDK changes.
+# See https://github.com/marketplace/actions/build-flipper-application-package-fap for more information
+
+on:
+  push:
+    ## put your main branch name under "branches"
+    #branches: 
+    #  - master 
+  pull_request:
+  schedule: 
+    # do a build every day
+    - cron: "1 1 * * *"
+
+jobs:
+  ufbt-build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        include:
+          - name: dev channel
+            sdk-channel: dev
+          - name: release channel
+            sdk-channel: release
+          # You can add unofficial channels here. See ufbt action docs for more info.
+    name: 'ufbt: Build for ${{ matrix.name }}'
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+      - name: Build with ufbt
+        uses: flipperdevices/flipperzero-ufbt-action@v0.1
+        id: build-app
+        with:
+          sdk-channel: ${{ matrix.sdk-channel }}
+      - name: Upload app artifacts
+        uses: actions/upload-artifact@v4
+        with:
+          # See ufbt action docs for other output variables
+          name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }}
+          path: ${{ steps.build-app.outputs.fap-artifacts }}

+ 4 - 0
weebo/.gitignore

@@ -0,0 +1,4 @@
+.vscode
+dist
+key_retail.h
+m.h

+ 1 - 0
weebo/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/bettse/weebo main /

+ 8 - 0
weebo/README.md

@@ -0,0 +1,8 @@
+# Weebo
+
+A flipper zero NTAG215 parser, writer, emulator, remixer, duplicator.
+
+![Demo Video](demo.mp4)
+
+**Put key_retail.bin into 'SD Card/apps_data/weebo/**
+

+ 2 - 0
weebo/acknowledgements.h

@@ -0,0 +1,2 @@
+const char* acknowledgements_text =
+    "This app developed using amiitool (https://github.com/socram8888/amiitool) by socram8888.\n";

+ 21 - 0
weebo/application.fam

@@ -0,0 +1,21 @@
+App(
+    appid="weebo",
+    name="Weebo",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="weebo_app",
+    stack_size=5 * 1024,
+    fap_icon="weebo.png",
+    fap_icon_assets="images",
+    fap_file_assets="assets",
+    fap_libs=["mbedtls"],
+    fap_private_libs=[
+        Lib(
+            name="amiitool",
+        ),
+    ],
+    fap_category="NFC",
+    fap_author="bettse",
+    fap_weburl="https://github.com/bettse/weebo",
+    fap_version="0.2",
+    fap_description="An NTAG215 parser, writer, emulator, remixer, duplicator",
+)

+ 542 - 0
weebo/assets/figure_ids.nfc

@@ -0,0 +1,542 @@
+Filetype: Flipper NFC resources
+Version: 1
+# ID: Name
+0000: Mario
+0001: Luigi
+0002: Peach
+0003: Yoshi
+0004: Rosalina and Luma
+0005: Bowser
+0006: Bowser Jr.
+0007: Wario
+0008: Donkey Kong
+0009: Diddy Kong
+000a: Toad
+0013: Daisy
+0014: Waluigi
+0017: Boo
+0024: Piranha Plant
+0080: Poochy
+0100: Link
+0101: Zelda
+0102: Ganondorf
+0103: Midna & Wolf Link
+0106: Urbosa
+0107: Mipha
+0108: Revali
+0140: Guardian
+0141: Bokoblin
+0180: Villager
+0181: Isabelle
+0182: K.K. Slider
+0183: Tom Nook
+0184: Timmy & Tommy
+0185: Timmy
+0186: Tommy
+0187: Sable
+0188: Mabel
+0189: Labelle
+018a: Reese
+018b: Cyrus
+018c: Digby
+018d: Rover
+018e: Resetti
+018f: Don Resetti
+0190: Brewster
+0191: Harriet
+0192: Blathers
+0193: Celeste
+0194: Kicks
+0195: Porter
+0196: Kapp'n
+0197: Leilani
+0198: Lelia
+0199: Grams
+019a: Chip
+019b: Nat
+019c: Phineas
+019d: Copper
+019e: Booker
+019f: Pete
+01a0: Pelly
+01a1: Phyllis
+01a2: Gulliver
+01a3: Joan
+01a4: Pascal
+01a5: Katarina
+01a6: Sahara
+01a7: Wendell
+01a8: Redd
+01a9: Gracie
+01aa: Lyle
+01ab: Pave
+01ac: Zipper
+01ad: Jack
+01ae: Franklin
+01af: Jingle
+01b0: Tortimer
+01b1: Dr. Shrunk
+01b3: Blanca
+01b4: Leif
+01b5: Luna
+01b6: Katie
+01c1: Lottie
+0200: Cyrano
+0201: Antonio
+0202: Pango
+0203: Anabelle
+0206: Snooty
+0208: Annalisa
+0209: Olaf
+0214: Teddy
+0215: Pinky
+0216: Curt
+0217: Chow
+0219: Nate
+021a: Groucho
+021b: Tutu
+021c: Ursala
+021d: Grizzly
+021e: Paula
+021f: Ike
+0220: Charlise
+0221: Beardo
+0222: Klaus
+022d: Jay
+022e: Robin
+022f: Anchovy
+0230: Twiggy
+0231: Jitters
+0232: Piper
+0233: Admiral
+0235: Midge
+0238: Jakey
+023c: Lucha
+023d: Jacques
+023e: Peck
+023f: Sparro
+024a: Angus
+024b: Rodeo
+024d: Stu
+024f: T-Bone
+0251: Coach
+0252: Vic
+025d: Bob
+025e: Mitzi
+025f: Rosie
+0260: Olivia
+0261: Kiki
+0262: Tangy
+0263: Punchy
+0264: Purrl
+0265: Moe
+0266: Kabuki
+0267: Kid Cat
+0268: Monique
+0269: Tabby
+026a: Stinky
+026b: Kitty
+026c: Tom
+026d: Merry
+026e: Felicity
+026f: Lolly
+0270: Ankha
+0271: Rudy
+0272: Katt
+027d: Bluebear
+027e: Maple
+027f: Poncho
+0280: Pudge
+0281: Kody
+0282: Stitches
+0283: Vladimir
+0284: Murphy
+0286: Olive
+0287: Cheri
+028a: June
+028b: Pekoe
+028c: Chester
+028d: Barold
+028e: Tammy
+028f: Marty
+0299: Goose
+029a: Benedict
+029b: Egbert
+029e: Ava
+02a2: Becky
+02a3: Plucky
+02a4: Knox
+02a5: Broffina
+02a6: Ken
+02b1: Patty
+02b2: Tipper
+02b7: Norma
+02b8: Naomi
+02c3: Alfonso
+02c4: Alli
+02c5: Boots
+02c7: Del
+02c9: Sly
+02ca: Gayle
+02cb: Drago
+02d6: Fauna
+02d7: Bam
+02d8: Zell
+02d9: Bruce
+02da: Deirdre
+02db: Lopez
+02dc: Fuchsia
+02dd: Beau
+02de: Diana
+02df: Erik
+02e0: Chelsea
+02ea: Goldie
+02eb: Butch
+02ec: Lucky
+02ed: Biskit
+02ee: Bones
+02ef: Portia
+02f0: Walker
+02f1: Daisy
+02f2: Cookie
+02f3: Maddie
+02f4: Bea
+02f8: Mac
+02f9: Marcel
+02fa: Benjamin
+02fb: Cherry
+02fc: Shep
+0307: Bill
+0308: Joey
+0309: Pate
+030a: Maelle
+030b: Deena
+030c: Pompom
+030d: Mallary
+030e: Freckles
+030f: Derwin
+0310: Drake
+0311: Scoot
+0312: Weber
+0313: Miranda
+0314: Ketchup
+0316: Gloria
+0317: Molly
+0318: Quillson
+0323: Opal
+0324: Dizzy
+0325: Big Top
+0326: Eloise
+0327: Margie
+0328: Paolo
+0329: Axel
+032a: Ellie
+032c: Tucker
+032d: Tia
+032e: Chai
+0338: Lily
+0339: Ribbot
+033a: Frobert
+033b: Camofrog
+033c: Drift
+033d: Wart Jr.
+033e: Puddles
+033f: Jeremiah
+0341: Tad
+0342: Cousteau
+0343: Huck
+0344: Prince
+0345: Jambette
+0347: Raddle
+0348: Gigi
+0349: Croque
+034a: Diva
+034b: Henry
+0356: Chevre
+0357: Nan
+0358: Billy
+035a: Gruff
+035c: Velma
+035d: Kidd
+035e: Pashmina
+0369: Cesar
+036a: Peewee
+036b: Boone
+036d: Louie
+036e: Boyd
+0370: Violet
+0371: Al
+0372: Rocket
+0373: Hans
+0374: Rilla
+037e: Hamlet
+037f: Apple
+0380: Graham
+0381: Rodney
+0382: Soleil
+0383: Clay
+0384: Flurry
+0385: Hamphrey
+0390: Rocco
+0392: Bubbles
+0393: Bertha
+0394: Biff
+0395: Bitty
+0398: Harry
+0399: Hippeux
+03a4: Buck
+03a5: Victoria
+03a6: Savannah
+03a7: Elmer
+03a8: Rosco
+03a9: Winnie
+03aa: Ed
+03ab: Cleo
+03ac: Peaches
+03ad: Annalise
+03ae: Clyde
+03af: Colton
+03b0: Papi
+03b1: Julian
+03bc: Yuka
+03bd: Alice
+03be: Melba
+03bf: Sydney
+03c0: Gonzo
+03c1: Ozzie
+03c4: Canberra
+03c5: Lyman
+03c6: Eugene
+03d1: Kitt
+03d2: Mathilda
+03d3: Carrie
+03d6: Astrid
+03d7: Sylvia
+03d9: Walt
+03da: Rooney
+03db: Marcie
+03e6: Bud
+03e7: Elvis
+03e8: Rex
+03ea: Leopold
+03ec: Mott
+03ed: Rory
+03ee: Lionel
+03fa: Nana
+03fb: Simon
+03fc: Tammi
+03fd: Monty
+03fe: Elise
+03ff: Flip
+0400: Shari
+0401: Deli
+040c: Dora
+040d: Limberg
+040e: Bella
+040f: Bree
+0410: Samson
+0411: Rod
+0414: Candi
+0415: Rizzo
+0416: Anicotti
+0418: Broccolo
+041a: Moose
+041b: Bettina
+041c: Greta
+041d: Penelope
+041e: Chadder
+0429: Octavian
+042a: Marina
+042b: Zucker
+0436: Queenie
+0437: Gladys
+0438: Sandy
+0439: Sprocket
+043b: Julia
+043c: Cranston
+043d: Phil
+043e: Blanche
+043f: Flora
+0440: Phoebe
+044b: Apollo
+044c: Amelia
+044d: Pierce
+044e: Buzz
+0450: Avery
+0451: Frank
+0452: Sterling
+0453: Keaton
+0454: Celia
+045f: Aurora
+0460: Roald
+0461: Cube
+0462: Hopper
+0463: Friga
+0464: Gwen
+0465: Puck
+0468: Wade
+0469: Boomer
+046a: Iggly
+046b: Tex
+046c: Flo
+046d: Sprinkle
+0478: Curly
+0479: Truffles
+047a: Rasher
+047b: Hugh
+047c: Lucy
+047d: Spork/Crackle
+0480: Cobb
+0481: Boris
+0482: Maggie
+0483: Peggy
+0485: Gala
+0486: Chops
+0487: Kevin
+0488: Pancetti
+0489: Agnes
+0494: Bunnie
+0495: Dotty
+0496: Coco
+0497: Snake
+0498: Gaston
+0499: Gabi
+049a: Pippy
+049b: Tiffany
+049c: Genji
+049d: Ruby
+049e: Doc
+049f: Claude
+04a0: Francine
+04a1: Chrissy
+04a2: Hopkins
+04a3: OHare
+04a4: Carmen
+04a5: Bonbon
+04a6: Cole
+04a7: Mira
+04a8: Toby
+04b2: Tank
+04b3: Rhonda
+04b4: Spike
+04b6: Hornsby
+04b9: Merengue
+04ba: Renée
+04c5: Vesta
+04c6: Baabara
+04c7: Eunice
+04c8: Stella
+04c9: Cashmere
+04cc: Willow
+04cd: Curlos
+04ce: Wendy
+04cf: Timbra
+04d0: Frita
+04d1: Muffy
+04d2: Pietro
+04d3: Etoile
+04dd: Peanut
+04de: Blaire
+04df: Filbert
+04e0: Pecan
+04e1: Nibbles
+04e2: Agent S
+04e3: Caroline
+04e4: Sally
+04e5: Static
+04e6: Mint
+04e7: Ricky
+04e8: Cally
+04ea: Tasha
+04eb: Sylvana
+04ec: Poppy
+04ed: Sheldon
+04ee: Marshal
+04ef: Hazel
+04fa: Rolf
+04fb: Rowan
+04fc: Tybalt
+04fd: Bangle
+04fe: Leonardo
+04ff: Claudia
+0500: Bianca
+050b: Chief
+050c: Lobo
+050d: Wolfgang
+050e: Whitney
+050f: Dobie
+0510: Freya
+0511: Fang
+0513: Vivian
+0514: Skye
+0515: Kyle
+0580: Fox
+0581: Falco
+0584: Wolf
+05c0: Samus
+05c2: Ridley
+0600: Captain Falcon
+0640: Olimar
+06c0: Little Mac
+0700: Wii Fit Trainer
+0740: Pit
+0741: Dark Pit
+0742: Palutena
+0780: Mr. G&W
+0781: R.O.B., Famicom
+0782: Duck Hunt
+07c0: Mii Figure
+0800: Inkling
+0801: Callie
+0802: Marie
+0a40: Min Min
+1906: Charizard
+1919: Pikachu
+1927: Jigglypuff
+1996: Mewtwo
+19ac: Pichu
+1ac0: Lucario
+1b92: Greninja
+1bd7: Incineroar
+1d00: Shadow Mewtwo
+1f00: Kirby
+1f01: Meta Knight
+1f02: King Dedede
+1f03: Waddle Dee
+2100: Marth
+2101: Ike
+2102: Lucina
+2103: Robin
+2104: Roy
+2105: Corrin
+2108: Chrom
+210b: Byleth
+2240: Shulk
+2242: Mythra
+2280: Ness
+2281: Lucas
+22c0: Chibi Robo
+3200: Sonic
+3240: Bayonetta
+3340: Pac-man
+33c0: Kazuya
+3480: Megaman
+34c0: Ryu
+34c1: Ken
+3500: Male Hunter
+3501: Nabiru
+3502: Rathian/Cheval
+3503: Barioth/Ayuria
+3504: Qurupeco/Dan
+35c0: Shovel Knight
+3600: Cloud Strife
+3601: Sephiroth
+3640: Hero
+37c1: Richter
+3840: Cloud Strife
+3980: Bayonetta
+3a00: Joker
+3b40: Banjo Kazooie
+3c80: Terry
+3dc0: Steve
+3dc1: Alex

+ 3 - 0
weebo/demo.mp4

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bf42f0762a4c9feaa303796b5bc74d1a67a60982c2bd2d189b11a8abffad3e4b
+size 3391584

+ 0 - 0
weebo/images/.gitkeep


BIN
weebo/images/DolphinNice_96x59.png


BIN
weebo/images/Nfc_10px.png


+ 21 - 0
weebo/lib/amiitool/LICENSE

@@ -0,0 +1,21 @@
+(c) 2015-2017 Marcos Del Sol Vives
+(c) 2016      javiMaD
+(c) 2016      Michael Armbruster
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 29 - 0
weebo/lib/amiitool/README.md

@@ -0,0 +1,29 @@
+amiitool
+========
+amiibo™ encryption/decryption/copy tool
+
+Usage
+=====
+**amiitool expects a binary dump. It will not work with XMLs or hexadecimal text files**. Aside from this, amiitool is very easy to use and has a very simple syntax.
+
+First, you have to specify an operation, either ```-e``` (encrypt and sign), ```-d``` (decrypt and check), ```-c``` (2 decrypt and check, copy appdata, encrypt)
+
+You need also to specify a file using ```-k [keys]``` switch, indicating which file contains the cryptographic master keys. The key is the concatenation of unfixed infos and locked secret keys.
+
+Optionally, you may also specify input and output files using ```-i [input]``` and ```-o [output]```. If input or output are unspecified, amiitool will default to stdin and stdout, respectively. This lets you pipe amiitool inputs and outputs with standard shell tools such as xxd.
+
+When decrypting, by default amiitool will be in strict mode, and will abort and raise an error if the cryptographic signature embedded in the encrypted dump is not valid. If you want to disable checking, use ```-l``` switch to put amiitool in lenient mode.
+
+The copy function permit to copy saved data from one amiibo™ to another one.
+
+Examples
+--------
+
+- Decryption "mario.bin" and displaying hex to terminal:
+   > amiitool -d -k retail.bin -i "mario.bin" | xxd
+
+- Encryption "modified.bin" to "signed.bin":
+   > amiitool -e -k retail.bin -i "modified.bin" -o "signed.bin"
+
+- Copy "mario2.bin" Saves (AppData) into "mario1.bin" and save to "mario3.bin"
+  > amiitool -c -k retail.bin -i "mario1.bin" -s "mario2.bin" -o "mario3.bin"

+ 181 - 0
weebo/lib/amiitool/amiibo.c

@@ -0,0 +1,181 @@
+/*
+ * (c) 2015-2017 Marcos Del Sol Vives
+ * (c) 2016      javiMaD
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "amiibo.h"
+#include "util.h"
+#include "mbedtls/md.h"
+#include "mbedtls/aes.h"
+#include <errno.h>
+#include "portable_endian.h"
+
+#define htobe16(x) __builtin_bswap16(x)
+#define be16toh(x) __builtin_bswap16(x)
+
+#define HMAC_POS_DATA 0x008
+#define HMAC_POS_TAG 0x1B4
+
+void nfc3d_amiibo_calc_seed(const uint8_t * dump, uint8_t * key) {
+	memcpy(key + 0x00, dump + 0x029, 0x02);
+	memset(key + 0x02, 0x00, 0x0E);
+	memcpy(key + 0x10, dump + 0x1D4, 0x08);
+	memcpy(key + 0x18, dump + 0x1D4, 0x08);
+	memcpy(key + 0x20, dump + 0x1E8, 0x20);
+}
+
+void nfc3d_amiibo_keygen(const nfc3d_keygen_masterkeys * masterKeys, const uint8_t * dump, nfc3d_keygen_derivedkeys * derivedKeys) {
+	uint8_t seed[NFC3D_KEYGEN_SEED_SIZE];
+
+	nfc3d_amiibo_calc_seed(dump, seed);
+	nfc3d_keygen(masterKeys, seed, derivedKeys);
+}
+
+void nfc3d_amiibo_cipher(const nfc3d_keygen_derivedkeys * keys, const uint8_t * in, uint8_t * out) {
+	mbedtls_aes_context aes;
+	size_t nc_off = 0;
+	unsigned char nonce_counter[16];
+	unsigned char stream_block[16];
+
+	mbedtls_aes_setkey_enc( &aes, keys->aesKey, 128 );
+	memset(nonce_counter, 0, sizeof(nonce_counter));
+	memset(stream_block, 0, sizeof(stream_block));
+	memcpy(nonce_counter, keys->aesIV, sizeof(nonce_counter));
+	mbedtls_aes_crypt_ctr( &aes, 0x188, &nc_off, nonce_counter, stream_block, in + 0x02C, out + 0x02C );
+
+	memcpy(out + 0x000, in + 0x000, 0x008);
+	// Data signature NOT copied
+	memcpy(out + 0x028, in + 0x028, 0x004);
+	// Tag signature NOT copied
+	memcpy(out + 0x1D4, in + 0x1D4, 0x034);
+}
+
+void nfc3d_amiibo_tag_to_internal(const uint8_t * tag, uint8_t * intl) {
+	memcpy(intl + 0x000, tag + 0x008, 0x008);
+	memcpy(intl + 0x008, tag + 0x080, 0x020);
+	memcpy(intl + 0x028, tag + 0x010, 0x024);
+	memcpy(intl + 0x04C, tag + 0x0A0, 0x168);
+	memcpy(intl + 0x1B4, tag + 0x034, 0x020);
+	memcpy(intl + 0x1D4, tag + 0x000, 0x008);
+	memcpy(intl + 0x1DC, tag + 0x054, 0x02C);
+}
+
+void nfc3d_amiibo_internal_to_tag(const uint8_t * intl, uint8_t * tag) {
+	memcpy(tag + 0x008, intl + 0x000, 0x008);
+	memcpy(tag + 0x080, intl + 0x008, 0x020);
+	memcpy(tag + 0x010, intl + 0x028, 0x024);
+	memcpy(tag + 0x0A0, intl + 0x04C, 0x168);
+	memcpy(tag + 0x034, intl + 0x1B4, 0x020);
+	memcpy(tag + 0x000, intl + 0x1D4, 0x008);
+	memcpy(tag + 0x054, intl + 0x1DC, 0x02C);
+}
+
+bool nfc3d_amiibo_unpack(const nfc3d_amiibo_keys * amiiboKeys, const uint8_t * tag, uint8_t * plain) {
+	uint8_t internal[NFC3D_AMIIBO_SIZE];
+	nfc3d_keygen_derivedkeys dataKeys;
+	nfc3d_keygen_derivedkeys tagKeys;
+
+	// Convert format
+	nfc3d_amiibo_tag_to_internal(tag, internal);
+
+	// Generate keys
+	nfc3d_amiibo_keygen(&amiiboKeys->data, internal, &dataKeys);
+	nfc3d_amiibo_keygen(&amiiboKeys->tag, internal, &tagKeys);
+
+	// Decrypt
+	nfc3d_amiibo_cipher(&dataKeys, internal, plain);
+
+	// Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
+	mbedtls_md_hmac( mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tagKeys.hmacKey, sizeof(tagKeys.hmacKey),	
+			 plain + 0x1D4, 0x34, plain + HMAC_POS_TAG );
+
+	// Regenerate data HMAC
+	mbedtls_md_hmac( mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), dataKeys.hmacKey, sizeof(dataKeys.hmacKey),	
+			 plain + 0x029, 0x1DF, plain + HMAC_POS_DATA );
+
+	return
+			memcmp(plain + HMAC_POS_DATA, internal + HMAC_POS_DATA, 32) == 0 &&
+			memcmp(plain + HMAC_POS_TAG, internal + HMAC_POS_TAG, 32) == 0;
+}
+
+void nfc3d_amiibo_pack(const nfc3d_amiibo_keys * amiiboKeys, const uint8_t * plain, uint8_t * tag) {
+	uint8_t cipher[NFC3D_AMIIBO_SIZE];
+	nfc3d_keygen_derivedkeys tagKeys;
+	nfc3d_keygen_derivedkeys dataKeys;
+
+	// Generate keys
+	nfc3d_amiibo_keygen(&amiiboKeys->tag, plain, &tagKeys);
+	nfc3d_amiibo_keygen(&amiiboKeys->data, plain, &dataKeys);
+
+	// Generate tag HMAC
+	mbedtls_md_hmac( mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tagKeys.hmacKey, sizeof(tagKeys.hmacKey),	
+			 plain + 0x1D4, 0x34, cipher + HMAC_POS_TAG );
+
+	// Init mbedtls HMAC context
+	mbedtls_md_context_t ctx;
+	mbedtls_md_init( &ctx );
+	mbedtls_md_setup( &ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1 );
+
+	// Generate data HMAC
+	mbedtls_md_hmac_starts( &ctx, dataKeys.hmacKey, sizeof(dataKeys.hmacKey) );
+	mbedtls_md_hmac_update( &ctx, plain + 0x029, 0x18B ); // Data
+	mbedtls_md_hmac_update( &ctx, cipher + HMAC_POS_TAG, 0x20 ); // Tag HMAC
+	mbedtls_md_hmac_update( &ctx, plain + 0x1D4, 0x34 ); // Here be dragons
+
+	mbedtls_md_hmac_finish( &ctx, cipher + HMAC_POS_DATA );
+
+	// HMAC cleanup
+	mbedtls_md_free( &ctx );
+
+	// Encrypt
+	nfc3d_amiibo_cipher(&dataKeys, plain, cipher);
+
+	// Convert back to hardware
+	nfc3d_amiibo_internal_to_tag(cipher, tag);
+}
+
+bool nfc3d_amiibo_load_keys(nfc3d_amiibo_keys * amiiboKeys, const char * path) {
+	FILE * f = fopen(path, "rb");
+	if (!f) {
+		return false;
+	}
+
+	if (!fread(amiiboKeys, sizeof(*amiiboKeys), 1, f)) {
+		fclose(f);
+		return false;
+	}
+	fclose(f);
+
+	if (
+		(amiiboKeys->data.magicBytesSize > 16) ||
+		(amiiboKeys->tag.magicBytesSize > 16)
+	) {
+		errno = EILSEQ;
+		return false;
+	}
+
+	return true;
+}
+
+
+void nfc3d_amiibo_copy_app_data(const uint8_t * src, uint8_t * dst) {
+
+	uint16_t *ami_nb_wr = (uint16_t*)(dst + 0x29);
+	uint16_t *cfg_nb_wr = (uint16_t*)(dst + 0xB4);
+
+	/* increment write counters */
+	*ami_nb_wr = htobe16(be16toh(*ami_nb_wr) + 1);
+	*cfg_nb_wr = htobe16(be16toh(*cfg_nb_wr) + 1);
+
+	/* copy flags */
+	dst[0x2C] = src[0x2C];
+	/* copy programID */
+	memcpy(dst + 0xAC, src + 0xAC, 8);
+	/* copy AppID */
+	memcpy(dst + 0xB6, src + 0xB6, 4);
+	/* copy AppData */
+	memcpy(dst + 0xDC, src + 0xDC, 216);
+}
+

+ 29 - 0
weebo/lib/amiitool/amiibo.h

@@ -0,0 +1,29 @@
+/*
+ * (c) 2015-2017 Marcos Del Sol Vives
+ * (c) 2016      javiMaD
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef HAVE_NFC3D_AMIIBO_H
+#define HAVE_NFC3D_AMIIBO_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "keygen.h"
+
+#define NFC3D_AMIIBO_SIZE 520
+
+#pragma pack(1)
+typedef struct {
+	nfc3d_keygen_masterkeys data;
+	nfc3d_keygen_masterkeys tag;
+} nfc3d_amiibo_keys;
+#pragma pack()
+
+bool nfc3d_amiibo_unpack(const nfc3d_amiibo_keys * amiiboKeys, const uint8_t * tag, uint8_t * plain);
+void nfc3d_amiibo_pack(const nfc3d_amiibo_keys * amiiboKeys, const uint8_t * plain, uint8_t * tag);
+bool nfc3d_amiibo_load_keys(nfc3d_amiibo_keys * amiiboKeys, const char * path);
+void nfc3d_amiibo_copy_app_data(const uint8_t * src, uint8_t * dst);
+
+#endif

+ 78 - 0
weebo/lib/amiitool/drbg.c

@@ -0,0 +1,78 @@
+/*
+ * (c) 2015-2017 Marcos Del Sol Vives
+ * (c) 2016      javiMaD
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "drbg.h"
+#include <assert.h>
+#include <string.h>
+#include <mbedtls/md.h>
+
+void nfc3d_drbg_init(nfc3d_drbg_ctx * ctx, const uint8_t * hmacKey, size_t hmacKeySize, const uint8_t * seed, size_t seedSize) {
+	assert(ctx != NULL);
+	assert(hmacKey != NULL);
+	assert(seed != NULL);
+	assert(seedSize <= NFC3D_DRBG_MAX_SEED_SIZE);
+
+	// Initialize primitives
+	ctx->used = false;
+	ctx->iteration = 0;
+	ctx->bufferSize = sizeof(ctx->iteration) + seedSize;
+
+	// The 16-bit counter is prepended to the seed when hashing, so we'll leave 2 bytes at the start
+	memcpy(ctx->buffer + sizeof(uint16_t), seed, seedSize);
+
+	// Initialize underlying HMAC context
+	mbedtls_md_init(&ctx->hmacCtx);
+	mbedtls_md_setup(&ctx->hmacCtx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
+	mbedtls_md_hmac_starts(&ctx->hmacCtx, hmacKey, hmacKeySize);
+}
+
+void nfc3d_drbg_step(nfc3d_drbg_ctx * ctx, uint8_t * output) {
+	assert(ctx != NULL);
+	assert(output != NULL);
+
+	if (ctx->used) {
+		// If used at least once, reinitialize the HMAC
+		mbedtls_md_hmac_reset(&ctx->hmacCtx);
+	} else {
+		ctx->used = true;
+	}
+
+	// Store counter in big endian, and increment it
+	ctx->buffer[0] = ctx->iteration >> 8;
+	ctx->buffer[1] = ctx->iteration >> 0;
+	ctx->iteration++;
+
+	// Do HMAC magic
+	mbedtls_md_hmac_update(&ctx->hmacCtx, ctx->buffer, ctx->bufferSize);
+	mbedtls_md_hmac_finish(&ctx->hmacCtx, output);
+}
+
+void nfc3d_drbg_cleanup(nfc3d_drbg_ctx * ctx) {
+	assert(ctx != NULL);
+	mbedtls_md_free(&ctx->hmacCtx);
+}
+
+void nfc3d_drbg_generate_bytes(const uint8_t * hmacKey, size_t hmacKeySize, const uint8_t * seed, size_t seedSize, uint8_t * output, size_t outputSize) {
+	uint8_t temp[NFC3D_DRBG_OUTPUT_SIZE];
+
+	nfc3d_drbg_ctx rngCtx;
+	nfc3d_drbg_init(&rngCtx, hmacKey, hmacKeySize, seed, seedSize);
+
+	while (outputSize > 0) {
+		if (outputSize < NFC3D_DRBG_OUTPUT_SIZE) {
+			nfc3d_drbg_step(&rngCtx, temp);
+			memcpy(output, temp, outputSize);
+			break;
+		}
+
+		nfc3d_drbg_step(&rngCtx, output);
+		output += NFC3D_DRBG_OUTPUT_SIZE;
+		outputSize -= NFC3D_DRBG_OUTPUT_SIZE;
+	}
+
+	nfc3d_drbg_cleanup(&rngCtx);
+}

+ 33 - 0
weebo/lib/amiitool/drbg.h

@@ -0,0 +1,33 @@
+/*
+ * (c) 2015-2017 Marcos Del Sol Vives
+ * (c) 2016      javiMaD
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef HAVE_NFC3D_DRBG_H
+#define HAVE_NFC3D_DRBG_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "mbedtls/md.h"
+
+#define NFC3D_DRBG_MAX_SEED_SIZE	480	/* Hardcoded max size in 3DS NFC module */
+#define NFC3D_DRBG_OUTPUT_SIZE		32	/* Every iteration generates 32 bytes */
+
+typedef struct {
+	mbedtls_md_context_t hmacCtx;
+	bool used;
+	uint16_t iteration;
+
+	uint8_t buffer[sizeof(uint16_t) + NFC3D_DRBG_MAX_SEED_SIZE];
+	size_t bufferSize;
+} nfc3d_drbg_ctx;
+
+void nfc3d_drbg_init(nfc3d_drbg_ctx * ctx, const uint8_t * hmacKey, size_t hmacKeySize, const uint8_t * seed, size_t seedSize);
+void nfc3d_drbg_step(nfc3d_drbg_ctx * ctx, uint8_t * output);
+void nfc3d_drbg_cleanup(nfc3d_drbg_ctx * ctx);
+void nfc3d_drbg_generate_bytes(const uint8_t * hmacKey, size_t hmacKeySize, const uint8_t * seed, size_t seedSize, uint8_t * output, size_t outputSize);
+
+#endif
+

+ 58 - 0
weebo/lib/amiitool/keygen.c

@@ -0,0 +1,58 @@
+/*
+ * (c) 2015-2017 Marcos Del Sol Vives
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "drbg.h"
+#include "keygen.h"
+#include "util.h"
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+void nfc3d_keygen_prepare_seed(const nfc3d_keygen_masterkeys * baseKeys, const uint8_t * baseSeed, uint8_t * output, size_t * outputSize) {
+	assert(baseKeys != NULL);
+	assert(baseSeed != NULL);
+	assert(output != NULL);
+	assert(outputSize != NULL);
+
+	uint8_t * start = output;
+
+	// 1: Copy whole type string
+	// output = memccpy(output, baseKeys->typeString, '\0', sizeof(baseKeys->typeString));
+  size_t strLen = strlen((char*)baseKeys->typeString);
+  size_t typeStringSize = sizeof(baseKeys->typeString);
+  output = (uint8_t*)strncpy((char*)output, baseKeys->typeString, typeStringSize);
+  output += strLen + 1;
+
+	// 2: Append (16 - magicBytesSize) from the input seed
+	size_t leadingSeedBytes = 16 - baseKeys->magicBytesSize;
+	memcpy(output, baseSeed, leadingSeedBytes);
+	output += leadingSeedBytes;
+
+	// 3: Append all bytes from magicBytes
+	memcpy(output, baseKeys->magicBytes, baseKeys->magicBytesSize);
+	output += baseKeys->magicBytesSize;
+
+	// 4: Append bytes 0x10-0x1F from input seed
+	memcpy(output, baseSeed + 0x10, 16);
+	output += 16;
+
+	// 5: Xor last bytes 0x20-0x3F of input seed with AES XOR pad and append them
+	unsigned int i;
+	for (i = 0; i < 32; i++) {
+		output[i] = baseSeed[i + 32] ^ baseKeys->xorPad[i];
+	}
+	output += 32;
+
+	*outputSize = output - start;
+}
+
+void nfc3d_keygen(const nfc3d_keygen_masterkeys * baseKeys, const uint8_t * baseSeed, nfc3d_keygen_derivedkeys * derivedKeys) {
+	uint8_t preparedSeed[NFC3D_DRBG_MAX_SEED_SIZE];
+	size_t preparedSeedSize;
+
+	nfc3d_keygen_prepare_seed(baseKeys, baseSeed, preparedSeed, &preparedSeedSize);
+	nfc3d_drbg_generate_bytes(baseKeys->hmacKey, sizeof(baseKeys->hmacKey), preparedSeed, preparedSeedSize, (uint8_t *) derivedKeys, sizeof(*derivedKeys));
+}

+ 34 - 0
weebo/lib/amiitool/keygen.h

@@ -0,0 +1,34 @@
+/*
+ * (c) 2015-2017 Marcos Del Sol Vives
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef HAVE_NFC3D_KEYGEN_H
+#define HAVE_NFC3D_KEYGEN_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#define NFC3D_KEYGEN_SEED_SIZE 64
+
+#pragma pack(1)
+typedef struct {
+	uint8_t hmacKey[16];
+	char typeString[14];
+	uint8_t rfu;
+	uint8_t magicBytesSize;
+	uint8_t magicBytes[16];
+	uint8_t xorPad[32];
+} nfc3d_keygen_masterkeys;
+
+typedef struct {
+	const uint8_t aesKey[16];
+	const uint8_t aesIV[16];
+	const uint8_t hmacKey[16];
+} nfc3d_keygen_derivedkeys;
+#pragma pack()
+
+void nfc3d_keygen(const nfc3d_keygen_masterkeys * baseKeys, const uint8_t * baseSeed, nfc3d_keygen_derivedkeys * derivedKeys);
+
+#endif

+ 121 - 0
weebo/lib/amiitool/portable_endian.h

@@ -0,0 +1,121 @@
+// "License": Public Domain
+// I, Mathias Panzenböck, place this file hereby into the public domain. Use it at your own risk for whatever you like.
+// In case there are jurisdictions that don't support putting things in the public domain you can also consider it to
+// be "dual licensed" under the BSD, MIT and Apache licenses, if you want to. This code is trivial anyway. Consider it
+// an example on how to get the endian conversion functions on different platforms.
+
+#ifndef PORTABLE_ENDIAN_H__
+#define PORTABLE_ENDIAN_H__
+
+#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__)
+
+#       define __WINDOWS__
+
+#endif
+
+#if defined(__linux__) || defined(__CYGWIN__)
+
+#       include <endian.h>
+
+#elif defined(__APPLE__)
+
+#       include <libkern/OSByteOrder.h>
+
+#       define htobe16(x) OSSwapHostToBigInt16(x)
+#       define htole16(x) OSSwapHostToLittleInt16(x)
+#       define be16toh(x) OSSwapBigToHostInt16(x)
+#       define le16toh(x) OSSwapLittleToHostInt16(x)
+
+#       define htobe32(x) OSSwapHostToBigInt32(x)
+#       define htole32(x) OSSwapHostToLittleInt32(x)
+#       define be32toh(x) OSSwapBigToHostInt32(x)
+#       define le32toh(x) OSSwapLittleToHostInt32(x)
+
+#       define htobe64(x) OSSwapHostToBigInt64(x)
+#       define htole64(x) OSSwapHostToLittleInt64(x)
+#       define be64toh(x) OSSwapBigToHostInt64(x)
+#       define le64toh(x) OSSwapLittleToHostInt64(x)
+
+#       define __BYTE_ORDER    BYTE_ORDER
+#       define __BIG_ENDIAN    BIG_ENDIAN
+#       define __LITTLE_ENDIAN LITTLE_ENDIAN
+#       define __PDP_ENDIAN    PDP_ENDIAN
+
+#elif defined(__OpenBSD__)
+
+#       include <sys/endian.h>
+
+#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
+
+#       include <sys/endian.h>
+
+#       define be16toh(x) betoh16(x)
+#       define le16toh(x) letoh16(x)
+
+#       define be32toh(x) betoh32(x)
+#       define le32toh(x) letoh32(x)
+
+#       define be64toh(x) betoh64(x)
+#       define le64toh(x) letoh64(x)
+
+#elif defined(__WINDOWS__)
+
+#       include <windows.h>
+
+#       if BYTE_ORDER == LITTLE_ENDIAN
+
+#               if defined(_MSC_VER)
+#                       include <stdlib.h>
+#                       define htobe16(x) _byteswap_ushort(x)
+#                       define htole16(x) (x)
+#                       define be16toh(x) _byteswap_ushort(x)
+#                       define le16toh(x) (x)
+
+#                       define htobe32(x) _byteswap_ulong(x)
+#                       define htole32(x) (x)
+#                       define be32toh(x) _byteswap_ulong(x)
+#                       define le32toh(x) (x)
+
+#                       define htobe64(x) _byteswap_uint64(x)
+#                       define htole64(x) (x)
+#                       define be64toh(x) _byteswap_uint64(x)
+#                       define le64toh(x) (x)
+
+#               elif defined(__GNUC__) || defined(__clang__)
+
+#                       define htobe16(x) __builtin_bswap16(x)
+#                       define htole16(x) (x)
+#                       define be16toh(x) __builtin_bswap16(x)
+#                       define le16toh(x) (x)
+
+#                       define htobe32(x) __builtin_bswap32(x)
+#                       define htole32(x) (x)
+#                       define be32toh(x) __builtin_bswap32(x)
+#                       define le32toh(x) (x)
+
+#                       define htobe64(x) __builtin_bswap64(x)
+#                       define htole64(x) (x)
+#                       define be64toh(x) __builtin_bswap64(x)
+#                       define le64toh(x) (x)
+#               else
+#                       //error platform not supported
+#               endif
+
+#       else
+
+#               //error byte order not supported
+
+#       endif
+
+#       define __BYTE_ORDER    BYTE_ORDER
+#       define __BIG_ENDIAN    BIG_ENDIAN
+#       define __LITTLE_ENDIAN LITTLE_ENDIAN
+#       define __PDP_ENDIAN    PDP_ENDIAN
+
+#else
+
+#       //error platform not supported
+
+#endif
+
+#endif

+ 27 - 0
weebo/lib/amiitool/util.c

@@ -0,0 +1,27 @@
+/*
+ * (c) 2015-2017 Marcos Del Sol Vives
+ * (c) 2016      javiMaD
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "util.h"
+#include <stdint.h>
+
+void printhex(void * data, size_t size) {
+	size_t i;
+	uint8_t * bytes = (uint8_t *) data;
+	for (i = 0; i < size; i++) {
+		if ((i % 16) > 0) {
+			printf(" ");
+		}
+		printf("%02X", bytes[i]);
+		if ((i % 16) == 15) {
+			printf("\n");
+		}
+	}
+	if ((i % 16) != 15) {
+		printf("\n");
+	}
+}
+

+ 17 - 0
weebo/lib/amiitool/util.h

@@ -0,0 +1,17 @@
+/*
+ * (c) 2015-2017 Marcos Del Sol Vives
+ * (c) 2016      javiMaD
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef HAVE_UTIL_H
+#define HAVE_UTIL_H
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+void printhex(void * data, size_t size);
+
+#endif

+ 21 - 0
weebo/lib/amiitool/version.c

@@ -0,0 +1,21 @@
+/*
+ * (c) 2017      Marcos Del Sol Vives
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <version.h>
+#include <stdio.h>
+
+const char * nfc3d_version_fork() {
+	// TODO: maybe this should go in another file?
+	return "socram";
+}
+
+uint32_t nfc3d_version_commit() {
+	return 0;
+}
+
+uint32_t nfc3d_version_build() {
+	return 0;
+}

+ 16 - 0
weebo/lib/amiitool/version.h

@@ -0,0 +1,16 @@
+/*
+ * (c) 2017      Marcos Del Sol Vives
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef HAVE_NFC3D_VERSION_H
+#define HAVE_NFC3D_VERSION_H
+
+#include <stdint.h>
+
+const char * nfc3d_version_fork();
+uint32_t nfc3d_version_build();
+uint32_t nfc3d_version_commit();
+
+#endif

+ 30 - 0
weebo/scenes/weebo_scene.c

@@ -0,0 +1,30 @@
+#include "weebo_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const weebo_on_enter_handlers[])(void*) = {
+#include "weebo_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const weebo_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "weebo_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const weebo_on_exit_handlers[])(void* context) = {
+#include "weebo_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers weebo_scene_handlers = {
+    .on_enter_handlers = weebo_on_enter_handlers,
+    .on_event_handlers = weebo_on_event_handlers,
+    .on_exit_handlers = weebo_on_exit_handlers,
+    .scene_num = WeeboSceneNum,
+};

+ 29 - 0
weebo/scenes/weebo_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) WeeboScene##id,
+typedef enum {
+#include "weebo_scene_config.h"
+    WeeboSceneNum,
+} WeeboScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers weebo_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "weebo_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "weebo_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "weebo_scene_config.h"
+#undef ADD_SCENE

+ 45 - 0
weebo/scenes/weebo_scene_acknowledgements.c

@@ -0,0 +1,45 @@
+#include "../weebo_i.h"
+#include <dolphin/dolphin.h>
+#include "../acknowledgements.h"
+
+void weebo_scene_acknowledgements_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    Weebo* weebo = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(weebo->view_dispatcher, result);
+    }
+}
+
+void weebo_scene_acknowledgements_on_enter(void* context) {
+    Weebo* weebo = context;
+
+    furi_string_reset(weebo->text_box_store);
+
+    FuriString* str = weebo->text_box_store;
+    furi_string_cat_printf(str, "%s\n", acknowledgements_text);
+
+    text_box_set_font(weebo->text_box, TextBoxFontText);
+    text_box_set_text(weebo->text_box, furi_string_get_cstr(weebo->text_box_store));
+    view_dispatcher_switch_to_view(weebo->view_dispatcher, WeeboViewTextBox);
+}
+
+bool weebo_scene_acknowledgements_on_event(void* context, SceneManagerEvent event) {
+    Weebo* weebo = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            consumed = scene_manager_previous_scene(weebo->scene_manager);
+        }
+    }
+    return consumed;
+}
+
+void weebo_scene_acknowledgements_on_exit(void* context) {
+    Weebo* weebo = context;
+
+    // Clear views
+    text_box_reset(weebo->text_box);
+}

+ 11 - 0
weebo/scenes/weebo_scene_config.h

@@ -0,0 +1,11 @@
+ADD_SCENE(weebo, main_menu, MainMenu)
+ADD_SCENE(weebo, keys_missing, KeysMissing)
+ADD_SCENE(weebo, file_select, FileSelect)
+ADD_SCENE(weebo, saved_menu, SavedMenu)
+ADD_SCENE(weebo, write, Write)
+ADD_SCENE(weebo, write_card_success, WriteCardSuccess)
+ADD_SCENE(weebo, emulate, Emulate)
+ADD_SCENE(weebo, info, Info)
+ADD_SCENE(weebo, save_name, SaveName)
+ADD_SCENE(weebo, save_success, SaveSuccess)
+ADD_SCENE(weebo, acknowledgements, Acknowledgements)

+ 101 - 0
weebo/scenes/weebo_scene_emulate.c

@@ -0,0 +1,101 @@
+#include "../weebo_i.h"
+#include <nfc/protocols/mf_ultralight/mf_ultralight_listener.h>
+
+#define TAG "SceneEmulate"
+
+void weebo_scene_emulate_widget_callback(GuiButtonType result, InputType type, void* context) {
+    furi_assert(context);
+    Weebo* weebo = context;
+
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(weebo->view_dispatcher, result);
+    }
+}
+
+void weebo_scene_emulate_draw_screen(Weebo* weebo) {
+    Widget* widget = weebo->widget;
+    FuriString* info_str = furi_string_alloc();
+    FuriString* uid_str = furi_string_alloc();
+    const MfUltralightData* data = nfc_device_get_data(weebo->nfc_device, NfcProtocolMfUltralight);
+
+    furi_string_cat_printf(info_str, "Emulating");
+    furi_string_cat_printf(
+        uid_str,
+        "%02X%02X%02X%02X%02X%02X%02X",
+        data->iso14443_3a_data->uid[0],
+        data->iso14443_3a_data->uid[1],
+        data->iso14443_3a_data->uid[2],
+        data->iso14443_3a_data->uid[3],
+        data->iso14443_3a_data->uid[4],
+        data->iso14443_3a_data->uid[5],
+        data->iso14443_3a_data->uid[6]);
+
+    widget_reset(widget);
+    widget_add_string_element(
+        widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(info_str));
+    widget_add_string_element(
+        widget, 64, 25, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(uid_str));
+
+    widget_add_button_element(
+        widget, GuiButtonTypeCenter, "Remix", weebo_scene_emulate_widget_callback, weebo);
+
+    furi_string_free(info_str);
+    furi_string_free(uid_str);
+
+    view_dispatcher_switch_to_view(weebo->view_dispatcher, WeeboViewWidget);
+}
+
+void weebo_scene_emulate_on_enter(void* context) {
+    Weebo* weebo = context;
+
+    nfc_device_load(weebo->nfc_device, furi_string_get_cstr(weebo->load_path));
+    const MfUltralightData* data = nfc_device_get_data(weebo->nfc_device, NfcProtocolMfUltralight);
+    weebo->listener = nfc_listener_alloc(weebo->nfc, NfcProtocolMfUltralight, data);
+    nfc_listener_start(weebo->listener, NULL, NULL);
+
+    weebo_scene_emulate_draw_screen(weebo);
+    weebo_blink_start(weebo);
+}
+
+bool weebo_scene_emulate_on_event(void* context, SceneManagerEvent event) {
+    Weebo* weebo = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(weebo->scene_manager, WeeboSceneEmulate, event.event);
+        if(event.event == GuiButtonTypeCenter) {
+            //stop listener
+            FURI_LOG_D(TAG, "Stopping listener");
+            nfc_listener_stop(weebo->listener);
+            nfc_listener_free(weebo->listener);
+            weebo->listener = NULL;
+
+            weebo_remix(weebo);
+            //start listener
+            FURI_LOG_D(TAG, "Starting listener");
+            const MfUltralightData* data =
+                nfc_device_get_data(weebo->nfc_device, NfcProtocolMfUltralight);
+            weebo->listener = nfc_listener_alloc(weebo->nfc, NfcProtocolMfUltralight, data);
+            nfc_listener_start(weebo->listener, NULL, NULL);
+
+            weebo_scene_emulate_draw_screen(weebo);
+
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void weebo_scene_emulate_on_exit(void* context) {
+    Weebo* weebo = context;
+
+    if(weebo->listener) {
+        nfc_listener_stop(weebo->listener);
+        nfc_listener_free(weebo->listener);
+        weebo->listener = NULL;
+    }
+
+    widget_reset(weebo->widget);
+    weebo_blink_stop(weebo);
+}

+ 25 - 0
weebo/scenes/weebo_scene_file_select.c

@@ -0,0 +1,25 @@
+#include "../weebo_i.h"
+
+void weebo_scene_file_select_on_enter(void* context) {
+    Weebo* weebo = context;
+
+    // Process file_select return
+    weebo_set_loading_callback(weebo, weebo_show_loading_popup, weebo);
+    if(weebo_file_select(weebo)) {
+        scene_manager_next_scene(weebo->scene_manager, WeeboSceneSavedMenu);
+    } else {
+        scene_manager_search_and_switch_to_previous_scene(
+            weebo->scene_manager, WeeboSceneMainMenu);
+    }
+    weebo_set_loading_callback(weebo, NULL, weebo);
+}
+
+bool weebo_scene_file_select_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void weebo_scene_file_select_on_exit(void* context) {
+    UNUSED(context);
+}

+ 48 - 0
weebo/scenes/weebo_scene_info.c

@@ -0,0 +1,48 @@
+#include "../weebo_i.h"
+#include <dolphin/dolphin.h>
+
+#define TAG "WeeboSceneInfo"
+
+void weebo_scene_info_on_enter(void* context) {
+    Weebo* weebo = context;
+
+    furi_string_reset(weebo->text_box_store);
+    FuriString* str = weebo->text_box_store;
+    FuriString* name = furi_string_alloc();
+
+    furi_string_cat_printf(str, "Info:\n");
+    if(weebo_get_figure_name(weebo, name)) {
+        furi_string_cat_printf(str, "%s\n", furi_string_get_cstr(name));
+    } else {
+        furi_string_cat_printf(str, "Unknown\n");
+    }
+    furi_string_cat_printf(str, "ID: %04x\n", weebo_get_figure_id(weebo));
+    if(weebo_get_figure_form(weebo, name)) {
+        furi_string_cat_printf(str, "Form: %s\n", furi_string_get_cstr(name));
+    }
+    if(weebo_get_figure_series(weebo, name)) {
+        furi_string_cat_printf(str, "Series: %s\n", furi_string_get_cstr(name));
+    }
+
+    furi_string_free(name);
+    text_box_set_font(weebo->text_box, TextBoxFontText);
+    text_box_set_text(weebo->text_box, furi_string_get_cstr(weebo->text_box_store));
+    view_dispatcher_switch_to_view(weebo->view_dispatcher, WeeboViewTextBox);
+}
+
+bool weebo_scene_info_on_event(void* context, SceneManagerEvent event) {
+    Weebo* weebo = context;
+    bool consumed = false;
+    UNUSED(weebo);
+
+    if(event.type == SceneManagerEventTypeCustom) {
+    }
+    return consumed;
+}
+
+void weebo_scene_info_on_exit(void* context) {
+    Weebo* weebo = context;
+
+    // Clear view
+    text_box_reset(weebo->text_box);
+}

+ 40 - 0
weebo/scenes/weebo_scene_keys_missing.c

@@ -0,0 +1,40 @@
+#include "../weebo_i.h"
+
+void weebo_scene_keys_missing_popup_callback(void* context) {
+    Weebo* weebo = context;
+    view_dispatcher_send_custom_event(weebo->view_dispatcher, WeeboCustomEventViewExit);
+}
+
+void weebo_scene_keys_missing_on_enter(void* context) {
+    Weebo* weebo = context;
+
+    Popup* popup = weebo->popup;
+
+    popup_set_header(popup, "key_retail.bin missing", 58, 28, AlignCenter, AlignCenter);
+    // popup_set_text(popup, "words", 64, 36, AlignCenter, AlignTop);
+    popup_set_context(weebo->popup, weebo);
+    popup_set_callback(popup, weebo_scene_keys_missing_popup_callback);
+
+    view_dispatcher_switch_to_view(weebo->view_dispatcher, WeeboViewPopup);
+}
+
+bool weebo_scene_keys_missing_on_event(void* context, SceneManagerEvent event) {
+    Weebo* weebo = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(weebo->scene_manager, WeeboSceneKeysMissing, event.event);
+        if(event.event == WeeboCustomEventViewExit) {
+            while(scene_manager_previous_scene(weebo->scene_manager))
+                ;
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void weebo_scene_keys_missing_on_exit(void* context) {
+    Weebo* weebo = context;
+    popup_reset(weebo->popup);
+}

+ 56 - 0
weebo/scenes/weebo_scene_main_menu.c

@@ -0,0 +1,56 @@
+#include "../weebo_i.h"
+
+#define TAG "SceneMainMenu"
+
+enum SubmenuIndex {
+    SubmenuIndexSaved = 0,
+    SubmenuIndexAcknowledgements,
+};
+
+void weebo_scene_main_menu_submenu_callback(void* context, uint32_t index) {
+    Weebo* weebo = context;
+    view_dispatcher_send_custom_event(weebo->view_dispatcher, index);
+}
+
+void weebo_scene_main_menu_on_enter(void* context) {
+    Weebo* weebo = context;
+    Submenu* submenu = weebo->submenu;
+    submenu_reset(submenu);
+
+    submenu_add_item(
+        submenu, "Saved", SubmenuIndexSaved, weebo_scene_main_menu_submenu_callback, weebo);
+    submenu_add_item(
+        submenu,
+        "Acknowledgements",
+        SubmenuIndexAcknowledgements,
+        weebo_scene_main_menu_submenu_callback,
+        weebo);
+
+    submenu_set_selected_item(
+        submenu, scene_manager_get_scene_state(weebo->scene_manager, WeeboSceneMainMenu));
+    view_dispatcher_switch_to_view(weebo->view_dispatcher, WeeboViewMenu);
+}
+
+bool weebo_scene_main_menu_on_event(void* context, SceneManagerEvent event) {
+    Weebo* weebo = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(weebo->scene_manager, WeeboSceneMainMenu, event.event);
+        if(event.event == SubmenuIndexSaved) {
+            scene_manager_next_scene(weebo->scene_manager, WeeboSceneFileSelect);
+            consumed = true;
+        } else if(event.event == SubmenuIndexAcknowledgements) {
+            scene_manager_next_scene(weebo->scene_manager, WeeboSceneAcknowledgements);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void weebo_scene_main_menu_on_exit(void* context) {
+    Weebo* weebo = context;
+
+    submenu_reset(weebo->submenu);
+}

+ 97 - 0
weebo/scenes/weebo_scene_save_name.c

@@ -0,0 +1,97 @@
+#include "../weebo_i.h"
+#include <lib/toolbox/name_generator.h>
+#include <gui/modules/validators.h>
+#include <toolbox/path.h>
+
+#define NFC_APP_EXTENSION     ".nfc"
+#define NFC_APP_PATH_PREFIX   "/ext/nfc"
+#define WEEBO_APP_FILE_PREFIX "weebo_"
+
+#define TAG "WeeboSceneSaveName"
+
+void weebo_scene_save_name_text_input_callback(void* context) {
+    Weebo* weebo = context;
+
+    view_dispatcher_send_custom_event(weebo->view_dispatcher, WeeboCustomEventTextInputDone);
+}
+
+void weebo_scene_save_name_on_enter(void* context) {
+    Weebo* weebo = context;
+
+    // Setup view
+    TextInput* text_input = weebo->text_input;
+    bool file_name_empty = false;
+    if(!strcmp(weebo->file_name, "")) {
+        name_generator_make_auto(
+            weebo->text_store, sizeof(weebo->text_store), WEEBO_APP_FILE_PREFIX);
+        file_name_empty = true;
+    } else {
+        // Add "_" suffix to make name unique
+        weebo->file_name[strlen(weebo->file_name)] = '_';
+        weebo->file_name[strlen(weebo->file_name)] = '\0';
+        weebo_text_store_set(weebo, weebo->file_name);
+    }
+    text_input_set_header_text(text_input, "Name the card");
+    text_input_set_result_callback(
+        text_input,
+        weebo_scene_save_name_text_input_callback,
+        weebo,
+        weebo->text_store,
+        sizeof(weebo->text_store),
+        file_name_empty);
+
+    FuriString* folder_path;
+    folder_path = furi_string_alloc_set(NFC_APP_PATH_PREFIX);
+
+    if(furi_string_end_with(weebo->load_path, NFC_APP_EXTENSION)) {
+        path_extract_dirname(furi_string_get_cstr(weebo->load_path), folder_path);
+    }
+
+    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
+        furi_string_get_cstr(folder_path), NFC_APP_EXTENSION, weebo->file_name);
+    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
+
+    view_dispatcher_switch_to_view(weebo->view_dispatcher, WeeboViewTextInput);
+
+    furi_string_free(folder_path);
+}
+
+bool weebo_scene_save_name_on_event(void* context, SceneManagerEvent event) {
+    Weebo* weebo = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == WeeboCustomEventTextInputDone) {
+            strlcpy(weebo->file_name, weebo->text_store, strlen(weebo->text_store) + 1);
+
+            FuriString* path = furi_string_alloc_set(NFC_APP_PATH_PREFIX);
+            if(furi_string_end_with(weebo->load_path, NFC_APP_EXTENSION)) {
+                path_extract_dirname(furi_string_get_cstr(weebo->load_path), path);
+            }
+            furi_string_cat_printf(path, "/%s%s", weebo->file_name, NFC_APP_EXTENSION);
+            FURI_LOG_D(TAG, "Saving to %s", furi_string_get_cstr(path));
+
+            if(nfc_device_save(weebo->nfc_device, furi_string_get_cstr(path))) {
+                scene_manager_next_scene(weebo->scene_manager, WeeboSceneSaveSuccess);
+                consumed = true;
+            } else {
+                consumed = scene_manager_search_and_switch_to_previous_scene(
+                    weebo->scene_manager, WeeboSceneMainMenu);
+            }
+
+            furi_string_free(path);
+        }
+    }
+    return consumed;
+}
+
+void weebo_scene_save_name_on_exit(void* context) {
+    Weebo* weebo = context;
+
+    // Clear view
+    void* validator_context = text_input_get_validator_callback_context(weebo->text_input);
+    text_input_set_validator(weebo->text_input, NULL, NULL);
+    validator_is_file_free(validator_context);
+
+    text_input_reset(weebo->text_input);
+}

+ 42 - 0
weebo/scenes/weebo_scene_save_success.c

@@ -0,0 +1,42 @@
+#include "../weebo_i.h"
+#include <dolphin/dolphin.h>
+
+void weebo_scene_save_success_popup_callback(void* context) {
+    Weebo* weebo = context;
+    view_dispatcher_send_custom_event(weebo->view_dispatcher, WeeboCustomEventViewExit);
+}
+
+void weebo_scene_save_success_on_enter(void* context) {
+    Weebo* weebo = context;
+    dolphin_deed(DolphinDeedNfcSave);
+
+    // Setup view
+    Popup* popup = weebo->popup;
+    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, weebo);
+    popup_set_callback(popup, weebo_scene_save_success_popup_callback);
+    popup_enable_timeout(popup);
+    view_dispatcher_switch_to_view(weebo->view_dispatcher, WeeboViewPopup);
+}
+
+bool weebo_scene_save_success_on_event(void* context, SceneManagerEvent event) {
+    Weebo* weebo = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == WeeboCustomEventViewExit) {
+            consumed = scene_manager_search_and_switch_to_previous_scene(
+                weebo->scene_manager, WeeboSceneSavedMenu);
+        }
+    }
+    return consumed;
+}
+
+void weebo_scene_save_success_on_exit(void* context) {
+    Weebo* weebo = context;
+
+    // Clear view
+    popup_reset(weebo->popup);
+}

+ 72 - 0
weebo/scenes/weebo_scene_saved_menu.c

@@ -0,0 +1,72 @@
+#include "../weebo_i.h"
+
+#define TAG "ScenesavedMenu"
+
+enum SubmenuIndex {
+    SubmenuIndexWrite = 0,
+    SubmenuIndexEmulate,
+    SubmenuIndexDuplicate,
+    SubmenuIndexInfo,
+};
+
+void weebo_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
+    Weebo* weebo = context;
+    view_dispatcher_send_custom_event(weebo->view_dispatcher, index);
+}
+
+void weebo_scene_saved_menu_on_enter(void* context) {
+    Weebo* weebo = context;
+    Submenu* submenu = weebo->submenu;
+    submenu_reset(submenu);
+
+    submenu_add_item(
+        submenu, "Write", SubmenuIndexWrite, weebo_scene_saved_menu_submenu_callback, weebo);
+    submenu_add_item(
+        submenu, "Emulate", SubmenuIndexEmulate, weebo_scene_saved_menu_submenu_callback, weebo);
+    submenu_add_item(
+        submenu,
+        "Duplicate",
+        SubmenuIndexDuplicate,
+        weebo_scene_saved_menu_submenu_callback,
+        weebo);
+    submenu_add_item(
+        submenu, "Info", SubmenuIndexInfo, weebo_scene_saved_menu_submenu_callback, weebo);
+
+    submenu_set_selected_item(
+        submenu, scene_manager_get_scene_state(weebo->scene_manager, WeeboSceneSavedMenu));
+    view_dispatcher_switch_to_view(weebo->view_dispatcher, WeeboViewMenu);
+}
+
+bool weebo_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
+    Weebo* weebo = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(weebo->scene_manager, WeeboSceneSavedMenu, event.event);
+        if(event.event == SubmenuIndexWrite) {
+            scene_manager_next_scene(weebo->scene_manager, WeeboSceneWrite);
+            consumed = true;
+        } else if(event.event == SubmenuIndexEmulate) {
+            scene_manager_next_scene(weebo->scene_manager, WeeboSceneEmulate);
+            consumed = true;
+        } else if(event.event == SubmenuIndexDuplicate) {
+            weebo_remix(weebo);
+            scene_manager_next_scene(weebo->scene_manager, WeeboSceneSaveName);
+            consumed = true;
+        } else if(event.event == SubmenuIndexInfo) {
+            scene_manager_next_scene(weebo->scene_manager, WeeboSceneInfo);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            weebo->scene_manager, WeeboSceneMainMenu);
+    }
+
+    return consumed;
+}
+
+void weebo_scene_saved_menu_on_exit(void* context) {
+    Weebo* weebo = context;
+
+    submenu_reset(weebo->submenu);
+}

+ 242 - 0
weebo/scenes/weebo_scene_write.c

@@ -0,0 +1,242 @@
+#include "../weebo_i.h"
+#include <nfc/protocols/mf_ultralight/mf_ultralight_poller.h>
+
+#define TAG "SceneWrite"
+
+static uint8_t SLB[] = {0x00, 0x00, 0x0F, 0xE0};
+static uint8_t CC[] = {0xf1, 0x10, 0xff, 0xee};
+static uint8_t DLB[] = {0x01, 0x00, 0x0f, 0xbd};
+static uint8_t CFG0[] = {0x00, 0x00, 0x00, 0x04};
+static uint8_t CFG1[] = {0x5f, 0x00, 0x00, 0x00};
+static uint8_t PACKRFUI[] = {0x80, 0x80, 0x00, 0x00};
+
+enum NTAG215Pages {
+    staticLockBits = 2,
+    capabilityContainer = 3,
+    userMemoryFirst = 4,
+    userMemoryLast = 129,
+    dynamicLockBits = 130,
+    cfg0 = 131,
+    cfg1 = 132,
+    pwd = 133,
+    pack = 134,
+    total = 135
+};
+
+NfcCommand weebo_scene_write_poller_callback(NfcGenericEvent event, void* context) {
+    furi_assert(event.protocol == NfcProtocolMfUltralight);
+    Weebo* weebo = context;
+    NfcCommand ret = NfcCommandContinue;
+
+    const MfUltralightPollerEvent* mf_ultralight_event = event.event_data;
+    MfUltralightPoller* poller = event.instance;
+
+    if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestMode) {
+        // no-op
+    } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) {
+        mf_ultralight_event->data->auth_context.skip_auth = true;
+    } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeReadSuccess) {
+        nfc_device_set_data(
+            weebo->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(weebo->poller));
+        const MfUltralightData* data =
+            nfc_device_get_data(weebo->nfc_device, NfcProtocolMfUltralight);
+
+        if(!mf_ultralight_is_all_data_read(data)) {
+            view_dispatcher_send_custom_event(weebo->view_dispatcher, WeeboCustomEventWrongCard);
+            ret = NfcCommandStop;
+            return ret;
+        }
+        if(data->type != MfUltralightTypeNTAG215) {
+            view_dispatcher_send_custom_event(weebo->view_dispatcher, WeeboCustomEventWrongCard);
+            ret = NfcCommandStop;
+            return ret;
+        }
+        view_dispatcher_send_custom_event(weebo->view_dispatcher, WeeboCustomEventCardDetected);
+
+        uint8_t PWD[4];
+        weebo_calculate_pwd(data->iso14443_3a_data->uid, PWD);
+
+        for(size_t p = 0; p < 2; p++) {
+            for(size_t i = 0; i < MF_ULTRALIGHT_PAGE_SIZE; i++) {
+                weebo->figure[NFC3D_UID_OFFSET + p * MF_ULTRALIGHT_PAGE_SIZE + i] =
+                    data->page[p].data[i];
+            }
+        }
+
+        uint8_t modified[NTAG215_SIZE];
+        nfc3d_amiibo_pack(&weebo->keys, weebo->figure, modified);
+
+        MfUltralightError error;
+        MfUltralightPage page;
+
+        // You might think it odd that I'm doing this writing "by hand" and not using the flipper SDK, but this is for two reasons:
+        // 1. The flipper SDK doesn't write beyond user memory
+        // 2. I order the writes from least destructive to most destructive, so that if something goes wrong, recovery _might_ be possible
+        do {
+            // user data
+            FURI_LOG_D(TAG, "Writing user data");
+            view_dispatcher_send_custom_event(
+                weebo->view_dispatcher, WeeboCustomEventWritingUserData);
+            for(size_t i = userMemoryFirst; i <= userMemoryLast; i++) {
+                memcpy(
+                    page.data, modified + (i * MF_ULTRALIGHT_PAGE_SIZE), MF_ULTRALIGHT_PAGE_SIZE);
+                error = mf_ultralight_poller_write_page(poller, i, &page);
+                if(error != MfUltralightErrorNone) {
+                    FURI_LOG_E(TAG, "Error writing page %zu: %d", i, error);
+                    view_dispatcher_send_custom_event(
+                        weebo->view_dispatcher, WeeboCustomEventWriteFailure);
+                    ret = NfcCommandStop;
+                    break;
+                }
+            }
+            if(error != MfUltralightErrorNone) {
+                ret = NfcCommandStop;
+                break;
+            }
+
+            view_dispatcher_send_custom_event(
+                weebo->view_dispatcher, WeeboCustomEventWritingConfigData);
+            FURI_LOG_D(TAG, "Writing config");
+
+            // pwd
+            memcpy(page.data, PWD, sizeof(PWD));
+            error = mf_ultralight_poller_write_page(poller, pwd, &page);
+            if(error != MfUltralightErrorNone) {
+                view_dispatcher_send_custom_event(
+                    weebo->view_dispatcher, WeeboCustomEventWriteFailure);
+                FURI_LOG_E(TAG, "Error writing PWD: %d", error);
+                ret = NfcCommandStop;
+                break;
+            }
+
+            // pack
+            memcpy(page.data, PACKRFUI, sizeof(PACKRFUI));
+            error = mf_ultralight_poller_write_page(poller, pack, &page);
+            if(error != MfUltralightErrorNone) {
+                view_dispatcher_send_custom_event(
+                    weebo->view_dispatcher, WeeboCustomEventWriteFailure);
+                FURI_LOG_E(TAG, "Error writing PACKRFUI: %d", error);
+                ret = NfcCommandStop;
+                break;
+            }
+
+            // capability container
+            memcpy(page.data, CC, sizeof(CC));
+            error = mf_ultralight_poller_write_page(poller, capabilityContainer, &page);
+            if(error != MfUltralightErrorNone) {
+                view_dispatcher_send_custom_event(
+                    weebo->view_dispatcher, WeeboCustomEventWriteFailure);
+                FURI_LOG_E(TAG, "Error writing CC: %d", error);
+                ret = NfcCommandStop;
+                break;
+            }
+
+            // cfg0
+            memcpy(page.data, CFG0, sizeof(CFG0));
+            error = mf_ultralight_poller_write_page(poller, cfg0, &page);
+            if(error != MfUltralightErrorNone) {
+                view_dispatcher_send_custom_event(
+                    weebo->view_dispatcher, WeeboCustomEventWriteFailure);
+                FURI_LOG_E(TAG, "Error writing CFG0: %d", error);
+                ret = NfcCommandStop;
+                break;
+            }
+
+            // cfg1
+            memcpy(page.data, CFG1, sizeof(CFG1));
+            error = mf_ultralight_poller_write_page(poller, cfg1, &page);
+            if(error != MfUltralightErrorNone) {
+                view_dispatcher_send_custom_event(
+                    weebo->view_dispatcher, WeeboCustomEventWriteFailure);
+                FURI_LOG_E(TAG, "Error writing CFG1: %d", error);
+                ret = NfcCommandStop;
+                break;
+            }
+
+            // dynamic lock bits
+            memcpy(page.data, DLB, sizeof(DLB));
+            error = mf_ultralight_poller_write_page(poller, dynamicLockBits, &page);
+            if(error != MfUltralightErrorNone) {
+                view_dispatcher_send_custom_event(
+                    weebo->view_dispatcher, WeeboCustomEventWriteFailure);
+                FURI_LOG_E(TAG, "Error writing DLB: %d", error);
+                ret = NfcCommandStop;
+                break;
+            }
+
+            // static lock bits
+            memcpy(page.data, SLB, sizeof(SLB));
+            error = mf_ultralight_poller_write_page(poller, staticLockBits, &page);
+            if(error != MfUltralightErrorNone) {
+                view_dispatcher_send_custom_event(
+                    weebo->view_dispatcher, WeeboCustomEventWriteFailure);
+                FURI_LOG_E(TAG, "Error writing SLB: %d", error);
+                ret = NfcCommandStop;
+                break;
+            }
+        } while(false);
+        ret = NfcCommandStop;
+        view_dispatcher_send_custom_event(weebo->view_dispatcher, WeeboCustomEventWriteSuccess);
+    } else {
+        FURI_LOG_D(TAG, "Unhandled event type: %d", mf_ultralight_event->type);
+    }
+    return ret;
+}
+
+void weebo_scene_write_on_enter(void* context) {
+    Weebo* weebo = context;
+    Popup* popup = weebo->popup;
+
+    popup_set_header(popup, "Present NTAG215", 58, 28, AlignCenter, AlignCenter);
+
+    weebo->poller = nfc_poller_alloc(weebo->nfc, NfcProtocolMfUltralight);
+    nfc_poller_start(weebo->poller, weebo_scene_write_poller_callback, weebo);
+
+    weebo_blink_start(weebo);
+
+    view_dispatcher_switch_to_view(weebo->view_dispatcher, WeeboViewPopup);
+}
+
+bool weebo_scene_write_on_event(void* context, SceneManagerEvent event) {
+    Weebo* weebo = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(weebo->scene_manager, WeeboSceneWrite, event.event);
+        if(event.event == WeeboCustomEventCardDetected) {
+            popup_set_text(weebo->popup, "Card detected", 64, 36, AlignCenter, AlignTop);
+            consumed = true;
+        } else if(event.event == WeeboCustomEventWritingUserData) {
+            popup_set_text(weebo->popup, "Writing user data", 64, 36, AlignCenter, AlignTop);
+            consumed = true;
+        } else if(event.event == WeeboCustomEventWritingConfigData) {
+            popup_set_text(weebo->popup, "Writing config data", 64, 36, AlignCenter, AlignTop);
+            consumed = true;
+        } else if(event.event == WeeboCustomEventWriteSuccess) {
+            popup_set_text(weebo->popup, "Write success", 64, 36, AlignCenter, AlignTop);
+            consumed = true;
+            scene_manager_next_scene(weebo->scene_manager, WeeboSceneWriteCardSuccess);
+        } else if(event.event == WeeboCustomEventWrongCard) {
+            popup_set_text(weebo->popup, "Wrong card", 64, 36, AlignCenter, AlignTop);
+            consumed = true;
+        } else if(event.event == WeeboCustomEventWriteFailure) {
+            popup_set_text(weebo->popup, "Write failure", 64, 36, AlignCenter, AlignTop);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void weebo_scene_write_on_exit(void* context) {
+    Weebo* weebo = context;
+
+    if(weebo->poller) {
+        nfc_poller_stop(weebo->poller);
+        nfc_poller_free(weebo->poller);
+        weebo->poller = NULL;
+    }
+
+    popup_reset(weebo->popup);
+    weebo_blink_stop(weebo);
+}

+ 51 - 0
weebo/scenes/weebo_scene_write_card_success.c

@@ -0,0 +1,51 @@
+#include "../weebo_i.h"
+#include <dolphin/dolphin.h>
+
+void weebo_scene_write_card_success_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    furi_assert(context);
+    Weebo* weebo = context;
+
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(weebo->view_dispatcher, result);
+    }
+}
+
+void weebo_scene_write_card_success_on_enter(void* context) {
+    Weebo* weebo = context;
+    Widget* widget = weebo->widget;
+    FuriString* str = furi_string_alloc_set("Write Success!");
+
+    dolphin_deed(DolphinDeedNfcReadSuccess);
+
+    // Send notification
+    notification_message(weebo->notifications, &sequence_success);
+
+    widget_add_string_element(
+        widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(str));
+
+    furi_string_free(str);
+
+    view_dispatcher_switch_to_view(weebo->view_dispatcher, WeeboViewWidget);
+}
+
+bool weebo_scene_write_card_success_on_event(void* context, SceneManagerEvent event) {
+    Weebo* weebo = context;
+    bool consumed = false;
+
+    // Jump back to main menu
+    if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            weebo->scene_manager, WeeboSceneMainMenu);
+    }
+    return consumed;
+}
+
+void weebo_scene_write_card_success_on_exit(void* context) {
+    Weebo* weebo = context;
+
+    // Clear view
+    widget_reset(weebo->widget);
+}

+ 505 - 0
weebo/weebo.c

@@ -0,0 +1,505 @@
+#include "weebo_i.h"
+
+#define TAG "weebo"
+
+#define WEEBO_KEY_RETAIL_FILENAME "key_retail"
+#define FIGURE_ID_LIST            APP_ASSETS_PATH("figure_ids.nfc")
+#define UNPACKED_FIGURE_ID        0x1dc
+#define NFC_APP_EXTENSION         ".nfc"
+#define NFC_APP_PATH_PREFIX       "/ext/nfc"
+
+static const char* nfc_resources_header = "Flipper NFC resources";
+static const uint32_t nfc_resources_file_version = 1;
+
+bool weebo_load_key_retail(Weebo* weebo) {
+    FuriString* path = furi_string_alloc();
+    bool parsed = false;
+    uint8_t buffer[160];
+    memset(buffer, 0, sizeof(buffer));
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    Stream* stream = file_stream_alloc(storage);
+
+    do {
+        furi_string_printf(
+            path, "%s/%s%s", STORAGE_APP_DATA_PATH_PREFIX, WEEBO_KEY_RETAIL_FILENAME, ".bin");
+
+        bool opened =
+            file_stream_open(stream, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING);
+        if(!opened) {
+            FURI_LOG_E(TAG, "Failed to open file");
+            break;
+        }
+
+        size_t bytes_read = stream_read(stream, buffer, sizeof(buffer));
+        if(bytes_read != sizeof(buffer)) {
+            FURI_LOG_E(TAG, "Insufficient data");
+            break;
+        }
+
+        memcpy(&weebo->keys, buffer, bytes_read);
+
+        // TODO: compare SHA1
+        parsed = true;
+    } while(false);
+
+    file_stream_close(stream);
+    furi_record_close(RECORD_STORAGE);
+    furi_string_free(path);
+
+    return parsed;
+}
+
+bool weebo_load_figure(Weebo* weebo, FuriString* path, bool show_dialog) {
+    bool parsed = false;
+    FuriString* reason = furi_string_alloc_set("Couldn't load file");
+    uint8_t buffer[NTAG215_SIZE];
+    memset(buffer, 0, sizeof(buffer));
+
+    if(weebo->loading_cb) {
+        weebo->loading_cb(weebo->loading_cb_ctx, true);
+    }
+
+    do {
+        NfcDevice* nfc_device = weebo->nfc_device;
+        if(!nfc_device_load(nfc_device, furi_string_get_cstr(path))) break;
+
+        NfcProtocol protocol = nfc_device_get_protocol(nfc_device);
+        if(protocol != NfcProtocolMfUltralight) {
+            furi_string_printf(reason, "Not Ultralight protocol");
+            break;
+        }
+
+        const MfUltralightData* data = nfc_device_get_data(nfc_device, NfcProtocolMfUltralight);
+        if(data->type != MfUltralightTypeNTAG215) {
+            furi_string_printf(reason, "Not NTAG215");
+            break;
+        }
+
+        if(!mf_ultralight_is_all_data_read(data)) {
+            furi_string_printf(reason, "Incomplete data");
+            break;
+        }
+
+        uint8_t* uid = data->iso14443_3a_data->uid;
+        uint8_t pwd[4];
+        weebo_calculate_pwd(uid, pwd);
+
+        if(memcmp(data->page[133].data, pwd, sizeof(pwd)) != 0) {
+            furi_string_printf(reason, "Wrong password");
+            break;
+        }
+
+        for(size_t i = 0; i < 135; i++) {
+            memcpy(
+                buffer + i * MF_ULTRALIGHT_PAGE_SIZE, data->page[i].data, MF_ULTRALIGHT_PAGE_SIZE);
+        }
+
+        if(!nfc3d_amiibo_unpack(&weebo->keys, buffer, weebo->figure)) {
+            FURI_LOG_E(TAG, "Failed to unpack");
+            break;
+        }
+
+        parsed = true;
+    } while(false);
+
+    if(weebo->loading_cb) {
+        weebo->loading_cb(weebo->loading_cb_ctx, false);
+    }
+
+    if((!parsed) && (show_dialog)) {
+        dialog_message_show_storage_error(weebo->dialogs, furi_string_get_cstr(reason));
+    }
+
+    furi_string_free(reason);
+    return parsed;
+}
+
+static bool
+    weebo_search_data(Storage* storage, const char* file_name, FuriString* key, FuriString* data) {
+    bool parsed = false;
+    FlipperFormat* file = flipper_format_file_alloc(storage);
+    FuriString* temp_str;
+    temp_str = furi_string_alloc();
+
+    do {
+        // Open file
+        if(!flipper_format_file_open_existing(file, file_name)) break;
+        // Read file header and version
+        uint32_t version = 0;
+        if(!flipper_format_read_header(file, temp_str, &version)) break;
+        if(furi_string_cmp_str(temp_str, nfc_resources_header) ||
+           (version != nfc_resources_file_version))
+            break;
+        if(!flipper_format_read_string(file, furi_string_get_cstr(key), data)) break;
+        parsed = true;
+    } while(false);
+
+    furi_string_free(temp_str);
+    flipper_format_free(file);
+    return parsed;
+}
+
+uint16_t weebo_get_figure_id(Weebo* weebo) {
+    uint16_t id = 0;
+    id |= weebo->figure[UNPACKED_FIGURE_ID + 0] << 8;
+    id |= weebo->figure[UNPACKED_FIGURE_ID + 1] << 0;
+    FURI_LOG_D(TAG, "id = %04x", id);
+    return id;
+}
+
+bool weebo_get_figure_form(Weebo* weebo, FuriString* name) {
+    bool parsed = false;
+    uint8_t form = weebo->figure[UNPACKED_FIGURE_ID + 3];
+    FURI_LOG_D(TAG, "form = %02x", form);
+    switch(form) {
+    case 0x00:
+        furi_string_set_str(name, "Figure");
+        parsed = true;
+        break;
+    case 0x01:
+        furi_string_set_str(name, "Card");
+        parsed = true;
+        break;
+    case 0x02:
+        furi_string_set_str(name, "Yarn");
+        parsed = true;
+        break;
+    default:
+        break;
+    }
+
+    return parsed;
+}
+
+bool weebo_get_figure_series(Weebo* weebo, FuriString* name) {
+    bool parsed = false;
+
+    uint8_t series_id = weebo->figure[UNPACKED_FIGURE_ID + 6];
+    switch(series_id) {
+    case 0x00:
+        furi_string_set_str(name, "Smash Bros");
+        parsed = true;
+        break;
+    case 0x01:
+        furi_string_set_str(name, "Mario Bros");
+        parsed = true;
+        break;
+    case 0x02:
+        furi_string_set_str(name, "Chibi Robo");
+        parsed = true;
+        break;
+    case 0x03:
+        furi_string_set_str(name, "Yarn Yoshi");
+        parsed = true;
+        break;
+    case 0x04:
+        furi_string_set_str(name, "Splatoon");
+        parsed = true;
+        break;
+    case 0x05:
+        furi_string_set_str(name, "Animal Crossing");
+        parsed = true;
+        break;
+    case 0x06:
+        furi_string_set_str(name, "8-bit Mario");
+        parsed = true;
+        break;
+    case 0x07:
+        furi_string_set_str(name, "Skylanders");
+        parsed = true;
+        break;
+    case 0x09:
+        furi_string_set_str(name, "Legend of Zelda");
+        parsed = true;
+        break;
+    case 0x0A:
+        furi_string_set_str(name, "Shovel Knight");
+        parsed = true;
+        break;
+    case 0x0C:
+        furi_string_set_str(name, "Kirby");
+        parsed = true;
+        break;
+    case 0x0D:
+        furi_string_set_str(name, "Pokken");
+        parsed = true;
+        break;
+    case 0x0F:
+        furi_string_set_str(name, "Monster Hunter");
+        parsed = true;
+        break;
+    default:
+        break;
+    }
+    return parsed;
+}
+
+bool weebo_get_figure_name(Weebo* weebo, FuriString* name) {
+    bool parsed = false;
+
+    uint16_t id = weebo_get_figure_id(weebo);
+
+    FuriString* key = furi_string_alloc_printf("%04x", id);
+    if(weebo_search_data(weebo->storage, FIGURE_ID_LIST, key, name)) {
+        parsed = true;
+    }
+    furi_string_free(key);
+    return parsed;
+}
+
+void weebo_set_loading_callback(Weebo* weebo, WeeboLoadingCallback callback, void* context) {
+    furi_assert(weebo);
+
+    weebo->loading_cb = callback;
+    weebo->loading_cb_ctx = context;
+}
+
+bool weebo_file_select(Weebo* weebo) {
+    furi_assert(weebo);
+    bool res = false;
+
+    FuriString* weebo_app_folder;
+
+    if(storage_dir_exists(weebo->storage, "/ext/nfc/SmashAmiibo")) {
+        weebo_app_folder = furi_string_alloc_set("/ext/nfc/SmashAmiibo");
+    } else if(storage_dir_exists(weebo->storage, "/ext/nfc/Amiibo")) {
+        weebo_app_folder = furi_string_alloc_set("/ext/nfc/Amiibo");
+    } else if(storage_dir_exists(weebo->storage, "/ext/nfc/Amiibos")) {
+        weebo_app_folder = furi_string_alloc_set("/ext/nfc/Amiibos");
+    } else if(storage_dir_exists(weebo->storage, "/ext/nfc/amiibo")) {
+        weebo_app_folder = furi_string_alloc_set("/ext/nfc/amiibo");
+    } else if(storage_dir_exists(weebo->storage, "/ext/nfc/amiibos")) {
+        weebo_app_folder = furi_string_alloc_set("/ext/nfc/amiibos");
+    } else {
+        weebo_app_folder = furi_string_alloc_set(NFC_APP_PATH_PREFIX);
+    }
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, NFC_APP_EXTENSION, &I_Nfc_10px);
+    browser_options.base_path = NFC_APP_PATH_PREFIX;
+
+    res = dialog_file_browser_show(
+        weebo->dialogs, weebo->load_path, weebo_app_folder, &browser_options);
+
+    furi_string_free(weebo_app_folder);
+    if(res) {
+        FuriString* filename;
+        filename = furi_string_alloc();
+        path_extract_filename(weebo->load_path, filename, true);
+        strncpy(weebo->file_name, furi_string_get_cstr(filename), WEEBO_FILE_NAME_MAX_LENGTH);
+        res = weebo_load_figure(weebo, weebo->load_path, true);
+        furi_string_free(filename);
+    }
+
+    return res;
+}
+
+bool weebo_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    Weebo* weebo = context;
+    return scene_manager_handle_custom_event(weebo->scene_manager, event);
+}
+
+bool weebo_back_event_callback(void* context) {
+    furi_assert(context);
+    Weebo* weebo = context;
+    return scene_manager_handle_back_event(weebo->scene_manager);
+}
+
+void weebo_tick_event_callback(void* context) {
+    furi_assert(context);
+    Weebo* weebo = context;
+    scene_manager_handle_tick_event(weebo->scene_manager);
+}
+
+Weebo* weebo_alloc() {
+    Weebo* weebo = malloc(sizeof(Weebo));
+
+    weebo->view_dispatcher = view_dispatcher_alloc();
+    weebo->scene_manager = scene_manager_alloc(&weebo_scene_handlers, weebo);
+    view_dispatcher_set_event_callback_context(weebo->view_dispatcher, weebo);
+    view_dispatcher_set_custom_event_callback(weebo->view_dispatcher, weebo_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(
+        weebo->view_dispatcher, weebo_back_event_callback);
+    view_dispatcher_set_tick_event_callback(
+        weebo->view_dispatcher, weebo_tick_event_callback, 100);
+
+    weebo->nfc = nfc_alloc();
+
+    // Nfc device
+    weebo->nfc_device = nfc_device_alloc();
+    nfc_device_set_loading_callback(weebo->nfc_device, weebo_show_loading_popup, weebo);
+
+    // Open GUI record
+    weebo->gui = furi_record_open(RECORD_GUI);
+    view_dispatcher_attach_to_gui(
+        weebo->view_dispatcher, weebo->gui, ViewDispatcherTypeFullscreen);
+
+    // Open Notification record
+    weebo->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    // Submenu
+    weebo->submenu = submenu_alloc();
+    view_dispatcher_add_view(
+        weebo->view_dispatcher, WeeboViewMenu, submenu_get_view(weebo->submenu));
+
+    // Popup
+    weebo->popup = popup_alloc();
+    view_dispatcher_add_view(weebo->view_dispatcher, WeeboViewPopup, popup_get_view(weebo->popup));
+
+    // Loading
+    weebo->loading = loading_alloc();
+    view_dispatcher_add_view(
+        weebo->view_dispatcher, WeeboViewLoading, loading_get_view(weebo->loading));
+
+    // Text Input
+    weebo->text_input = text_input_alloc();
+    view_dispatcher_add_view(
+        weebo->view_dispatcher, WeeboViewTextInput, text_input_get_view(weebo->text_input));
+
+    // Number Input
+    weebo->number_input = number_input_alloc();
+    view_dispatcher_add_view(
+        weebo->view_dispatcher, WeeboViewNumberInput, number_input_get_view(weebo->number_input));
+
+    // TextBox
+    weebo->text_box = text_box_alloc();
+    view_dispatcher_add_view(
+        weebo->view_dispatcher, WeeboViewTextBox, text_box_get_view(weebo->text_box));
+    weebo->text_box_store = furi_string_alloc();
+
+    // Custom Widget
+    weebo->widget = widget_alloc();
+    view_dispatcher_add_view(
+        weebo->view_dispatcher, WeeboViewWidget, widget_get_view(weebo->widget));
+
+    weebo->storage = furi_record_open(RECORD_STORAGE);
+    weebo->dialogs = furi_record_open(RECORD_DIALOGS);
+    weebo->load_path = furi_string_alloc();
+
+    weebo->keys_loaded = false;
+
+    return weebo;
+}
+
+void weebo_free(Weebo* weebo) {
+    furi_assert(weebo);
+
+    nfc_free(weebo->nfc);
+
+    // Nfc device
+    nfc_device_free(weebo->nfc_device);
+
+    // Submenu
+    view_dispatcher_remove_view(weebo->view_dispatcher, WeeboViewMenu);
+    submenu_free(weebo->submenu);
+
+    // Popup
+    view_dispatcher_remove_view(weebo->view_dispatcher, WeeboViewPopup);
+    popup_free(weebo->popup);
+
+    // Loading
+    view_dispatcher_remove_view(weebo->view_dispatcher, WeeboViewLoading);
+    loading_free(weebo->loading);
+
+    // TextInput
+    view_dispatcher_remove_view(weebo->view_dispatcher, WeeboViewTextInput);
+    text_input_free(weebo->text_input);
+
+    // NumberInput
+    view_dispatcher_remove_view(weebo->view_dispatcher, WeeboViewNumberInput);
+    number_input_free(weebo->number_input);
+
+    // TextBox
+    view_dispatcher_remove_view(weebo->view_dispatcher, WeeboViewTextBox);
+    text_box_free(weebo->text_box);
+    furi_string_free(weebo->text_box_store);
+
+    // Custom Widget
+    view_dispatcher_remove_view(weebo->view_dispatcher, WeeboViewWidget);
+    widget_free(weebo->widget);
+
+    // View Dispatcher
+    view_dispatcher_free(weebo->view_dispatcher);
+
+    // Scene Manager
+    scene_manager_free(weebo->scene_manager);
+
+    // GUI
+    furi_record_close(RECORD_GUI);
+    weebo->gui = NULL;
+
+    // Notifications
+    furi_record_close(RECORD_NOTIFICATION);
+    weebo->notifications = NULL;
+
+    furi_string_free(weebo->load_path);
+    furi_record_close(RECORD_STORAGE);
+    furi_record_close(RECORD_DIALOGS);
+
+    free(weebo);
+}
+
+void weebo_text_store_set(Weebo* weebo, const char* text, ...) {
+    va_list args;
+    va_start(args, text);
+
+    vsnprintf(weebo->text_store, sizeof(weebo->text_store), text, args);
+
+    va_end(args);
+}
+
+void weebo_text_store_clear(Weebo* weebo) {
+    memset(weebo->text_store, 0, sizeof(weebo->text_store));
+}
+
+static const NotificationSequence weebo_sequence_blink_start_blue = {
+    &message_blink_start_10,
+    &message_blink_set_color_blue,
+    &message_do_not_reset,
+    NULL,
+};
+
+static const NotificationSequence weebo_sequence_blink_stop = {
+    &message_blink_stop,
+    NULL,
+};
+
+void weebo_blink_start(Weebo* weebo) {
+    notification_message(weebo->notifications, &weebo_sequence_blink_start_blue);
+}
+
+void weebo_blink_stop(Weebo* weebo) {
+    notification_message(weebo->notifications, &weebo_sequence_blink_stop);
+}
+
+void weebo_show_loading_popup(void* context, bool show) {
+    Weebo* weebo = context;
+
+    if(show) {
+        // Raise timer priority so that animations can play
+        furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
+        view_dispatcher_switch_to_view(weebo->view_dispatcher, WeeboViewLoading);
+    } else {
+        // Restore default timer priority
+        furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal);
+    }
+}
+
+int32_t weebo_app(void* p) {
+    UNUSED(p);
+    Weebo* weebo = weebo_alloc();
+
+    weebo->keys_loaded = weebo_load_key_retail(weebo);
+    if(weebo->keys_loaded) {
+        scene_manager_next_scene(weebo->scene_manager, WeeboSceneMainMenu);
+    } else {
+        scene_manager_next_scene(weebo->scene_manager, WeeboSceneKeysMissing);
+    }
+
+    view_dispatcher_run(weebo->view_dispatcher);
+
+    weebo_free(weebo);
+
+    return 0;
+}

+ 8 - 0
weebo/weebo.h

@@ -0,0 +1,8 @@
+#pragma once
+
+typedef struct Weebo Weebo;
+
+uint16_t weebo_get_figure_id(Weebo* weebo);
+bool weebo_get_figure_name(Weebo* weebo, FuriString* name);
+bool weebo_get_figure_form(Weebo* weebo, FuriString* name);
+bool weebo_get_figure_series(Weebo* weebo, FuriString* name);

BIN
weebo/weebo.png


+ 44 - 0
weebo/weebo_common.c

@@ -0,0 +1,44 @@
+#include "weebo_common.h"
+
+#define TAG "WeeboCommon"
+
+void weebo_calculate_pwd(uint8_t* uid, uint8_t* pwd) {
+    pwd[0] = uid[1] ^ uid[3] ^ 0xAA;
+    pwd[1] = uid[2] ^ uid[4] ^ 0x55;
+    pwd[2] = uid[3] ^ uid[5] ^ 0xAA;
+    pwd[3] = uid[4] ^ uid[6] ^ 0x55;
+}
+
+void weebo_remix(Weebo* weebo) {
+    uint8_t PWD[4];
+    uint8_t UID[8];
+    uint8_t modified[NTAG215_SIZE];
+    MfUltralightData* data = mf_ultralight_alloc();
+    nfc_device_copy_data(weebo->nfc_device, NfcProtocolMfUltralight, data);
+
+    //random uid
+    FURI_LOG_D(TAG, "Generating random UID");
+    UID[0] = 0x04;
+    furi_hal_random_fill_buf(UID + 1, 6);
+    UID[7] = UID[3] ^ UID[4] ^ UID[5] ^ UID[6];
+    memcpy(weebo->figure + NFC3D_UID_OFFSET, UID, 8);
+    memcpy(data->iso14443_3a_data->uid, UID, 7);
+
+    //pack
+    nfc3d_amiibo_pack(&weebo->keys, weebo->figure, modified);
+
+    //copy data in
+    for(size_t i = 0; i < 130; i++) {
+        memcpy(
+            data->page[i].data, modified + i * MF_ULTRALIGHT_PAGE_SIZE, MF_ULTRALIGHT_PAGE_SIZE);
+    }
+
+    //new pwd
+    weebo_calculate_pwd(data->iso14443_3a_data->uid, PWD);
+    memcpy(data->page[133].data, PWD, sizeof(PWD));
+
+    //set data
+    nfc_device_set_data(weebo->nfc_device, NfcProtocolMfUltralight, data);
+
+    mf_ultralight_free(data);
+}

+ 7 - 0
weebo/weebo_common.h

@@ -0,0 +1,7 @@
+#pragma once
+
+#include <furi.h>
+#include <weebo_i.h>
+
+void weebo_calculate_pwd(uint8_t* uid, uint8_t* pwd);
+void weebo_remix(Weebo* weebo);

+ 126 - 0
weebo/weebo_i.h

@@ -0,0 +1,126 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <notification/notification_messages.h>
+#include <storage/storage.h>
+#include <dialogs/dialogs.h>
+
+#include <gui/modules/submenu.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/loading.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/number_input.h>
+#include <gui/modules/text_box.h>
+#include <gui/modules/widget.h>
+
+#include <input/input.h>
+#include <lib/flipper_format/flipper_format.h>
+#include <lib/toolbox/path.h>
+
+#include <lib/nfc/nfc.h>
+#include <nfc/nfc_poller.h>
+#include <nfc/nfc_listener.h>
+#include <nfc/nfc_device.h>
+#include <nfc/protocols/mf_ultralight/mf_ultralight_poller.h>
+#include <nfc/protocols/mf_ultralight/mf_ultralight_listener.h>
+
+#include <lib/toolbox/stream/stream.h>
+#include <lib/toolbox/stream/file_stream.h>
+
+/* generated by fbt from .png files in images folder */
+#include <weebo_icons.h>
+
+#include <amiibo.h>
+
+#include "weebo.h"
+#include "weebo_common.h"
+#include "scenes/weebo_scene.h"
+
+#define WEEBO_TEXT_STORE_SIZE      128
+#define WEEBO_FILE_NAME_MAX_LENGTH 64
+
+#define NTAG215_SIZE     540
+#define NFC3D_UID_OFFSET 0x1D4
+#define PAGE_SIZE        4
+
+enum WeeboCustomEvent {
+    // Reserve first 100 events for button types and indexes, starting from 0
+    WeeboCustomEventReserved = 100,
+
+    WeeboCustomEventViewExit,
+    WeeboCustomEventTextInputDone,
+    WeeboCustomEventNumberInputDone,
+    // Card writing
+    WeeboCustomEventCardDetected,
+    WeeboCustomEventWritingUserData,
+    WeeboCustomEventWritingConfigData,
+    WeeboCustomEventWriteSuccess,
+    WeeboCustomEventWriteFailure,
+    WeeboCustomEventWrongCard,
+};
+
+typedef void (*WeeboLoadingCallback)(void* context, bool state);
+
+struct Weebo {
+    ViewDispatcher* view_dispatcher;
+    Gui* gui;
+    NotificationApp* notifications;
+    SceneManager* scene_manager;
+    Storage* storage;
+
+    char text_store[WEEBO_TEXT_STORE_SIZE + 1];
+    FuriString* text_box_store;
+
+    // Common Views
+    Submenu* submenu;
+    Popup* popup;
+    Loading* loading;
+    TextInput* text_input;
+    NumberInput* number_input;
+    TextBox* text_box;
+    Widget* widget;
+    DialogsApp* dialogs;
+
+    Nfc* nfc;
+    NfcPoller* poller;
+    NfcListener* listener;
+    NfcDevice* nfc_device;
+
+    FuriString* load_path;
+    char file_name[WEEBO_FILE_NAME_MAX_LENGTH + 1];
+
+    bool keys_loaded;
+    nfc3d_amiibo_keys keys;
+
+    WeeboLoadingCallback loading_cb;
+    void* loading_cb_ctx;
+
+    uint8_t figure[NFC3D_AMIIBO_SIZE];
+};
+
+typedef enum {
+    WeeboViewMenu,
+    WeeboViewPopup,
+    WeeboViewLoading,
+    WeeboViewTextInput,
+    WeeboViewNumberInput,
+    WeeboViewTextBox,
+    WeeboViewWidget,
+} WeeboView;
+
+void weebo_text_store_set(Weebo* weebo, const char* text, ...);
+
+void weebo_text_store_clear(Weebo* weebo);
+
+void weebo_blink_start(Weebo* weebo);
+
+void weebo_blink_stop(Weebo* weebo);
+
+void weebo_show_loading_popup(void* context, bool show);
+
+void weebo_set_loading_callback(Weebo* weebo, WeeboLoadingCallback callback, void* context);
+bool weebo_file_select(Weebo* weebo);