diff --git a/amnet-native/App.tsx b/amnet-native/App.tsx index 9202455..6d44af5 100644 --- a/amnet-native/App.tsx +++ b/amnet-native/App.tsx @@ -77,15 +77,17 @@ const Tab = createBottomTabNavigator(); const Stack = createNativeStackNavigator(); function AppContent() { - return ( } screenOptions={{headerShown: false}}> - ()}}/> + return ( + } screenOptions={{headerShown: false}}> + ()}}/> - ()}}/> - ) + ()}}/> + + ) } export default function AppRoot() { diff --git a/amnet-native/app.json b/amnet-native/app.json index 8295f61..4cf1955 100644 --- a/amnet-native/app.json +++ b/amnet-native/app.json @@ -10,25 +10,12 @@ "ios": { "bundleIdentifier": "moe.ppc.amnet", "supportsTablet": true, - "associatedDomains": [ - "applinks:card.ppc.moe" - ], "infoPlist": { "NSAppTransportSecurity": { "NSAllowsArbitraryLoads": true }, "UIDesignRequiresCompatibility": true, - "UIViewControllerBasedStatusBarAppearance": false, - "NFCReaderUsageDescription": "AMNet uses NFC to scan Physical IC Cards", - "com.apple.developer.nfc.readersession.felica.systemcodes": [ - "0003", - "88b4" - ] - }, - "entitlements": { - "com.apple.developer.nfc.readersession.formats": [ - "TAG" - ] + "UIViewControllerBasedStatusBarAppearance": false }, "config": { "usesNonExemptEncryption": false @@ -36,7 +23,7 @@ }, "android": { "package": "moe.ppc.amnet", - "versionCode": 8, + "versionCode": 9, "adaptiveIcon": { "foregroundImage": "./assets/icon-android.png", "backgroundColor": "#DED8FD" diff --git a/amnet-native/app/ServerCardIn.tsx b/amnet-native/app/ServerCardIn.tsx index 022e305..739e4a8 100644 --- a/amnet-native/app/ServerCardIn.tsx +++ b/amnet-native/app/ServerCardIn.tsx @@ -1,7 +1,6 @@ import {BSON} from "realm"; -import NfcManager from "react-native-nfc-manager"; +import {ActivityIndicator, Button, Text, useTheme} from "react-native-paper"; import React, {useCallback, useEffect, useLayoutEffect, useMemo, useState} from "react"; -import {ActivityIndicator, Button, IconButton, Text, useTheme} from "react-native-paper"; import {Alert, Platform, RefreshControl, ScrollView, Share, StyleSheet, View} from "react-native"; import { CircleCheck, @@ -13,7 +12,6 @@ import { WifiOff, TvMinimalPlay, Star, - SmartphoneNfc, SquareArrowOutUpRight } from "lucide-react-native"; @@ -26,7 +24,6 @@ import RealmContext from "../models/RealmContext"; import {AMServer} from "../models/AMServer"; import {AMCard} from "../models/AMCard"; -import {felicaCardReadAsync, icCard0008Number, ICType} from "../lib/iccard"; import { AMCardInRequest, AMServerConnectionInfo, @@ -82,16 +79,6 @@ export default function ServerCardIn({route, navigation}) { } }, [realm, server, navigation]); - // nfc support - const [nfcSupported, setNfcSupported] = useState(Platform.select({android: false, default: null})); - useEffect(() => { - if (nfcSupported !== null) { - return; - } - - NfcManager.isSupported().then(setNfcSupported); - }, []); - // server state const [serverRefreshing, setServerRefreshing] = useState(false); const [serverReloadCounter, setServerReloadCounter] = useState(0); @@ -101,7 +88,6 @@ export default function ServerCardIn({route, navigation}) { const [cardInCompleted, setCardInCompleted] = useState(null); const disableCardIn = useMemo(() => cardInCompleted !== null || !serverConnectionSuccess, [cardInCompleted, serverConnectionSuccess]); - const serverDefaultCard = useMemo(() => { if (server?.isValid() !== true || !server.gameId) { return null; @@ -211,38 +197,6 @@ export default function ServerCardIn({route, navigation}) { return () => abortController.abort(); }, [server]); - const performPhysicalCardIn = useCallback(async () => { - const card = await felicaCardReadAsync(); - - if (!card) { - return; - } - - const savedCard = cards.find(x => x.physicalCardData?.cardIDm === card.idm); - - if (savedCard) { - setUserSelectedCard(savedCard); - cardInCallback(savedCard); - return; - } - - setCardInCompleted(false); - - const cardId = card.type !== ICType.Amusement ? icCard0008Number(card.idm) : card.accessCode; - const abortController = new AbortController(); - - performCardIn(server, {cardId, physicalCardIDm: card.idm}, abortController.signal).then(r => { - setCardInCompleted(r); - }).catch(() => { - setCardInCompleted(false); - }).finally(() => { - setTimeout(() => setCardInCompleted(null), 3000); - }); - - setTimeout(() => abortController.abort(), 5000); - return () => abortController.abort(); - }, [server]); - const performRefresh = () => { setServerRefreshing(true); setServerReloadCounter(serverReloadCounter + 1); @@ -262,8 +216,7 @@ export default function ServerCardIn({route, navigation}) { } return ( - }> + }> @@ -274,28 +227,16 @@ export default function ServerCardIn({route, navigation}) { actionIcon={}> {serverDefaultCard?.code == selectedCard.code && } - - - {nfcSupported === true && ( - }/> - )} - + icon={() => }> + {cardInCompleted === null ? "Card In" : cardInCompleted ? "Card In Success" : "Failed"} + : }/> @@ -329,8 +270,7 @@ export default function ServerCardIn({route, navigation}) { {serverDefaultCard?.code == card.code && } )) : - }/> + }/> } )} diff --git a/amnet-native/app/card/CardAddModal.tsx b/amnet-native/app/card/CardAddModal.tsx index ee0e482..a836e3b 100644 --- a/amnet-native/app/card/CardAddModal.tsx +++ b/amnet-native/app/card/CardAddModal.tsx @@ -1,15 +1,12 @@ +import React from "react"; import {StatusBar} from "expo-status-bar"; -import NfcManager from "react-native-nfc-manager"; - -import React, {useEffect, useState} from "react"; import {Platform, TouchableOpacity, View} from "react-native"; import {createNativeStackNavigator} from "@react-navigation/native-stack"; import {Card, Text, useTheme} from "react-native-paper"; -import {ShieldBan, SmartphoneNfc, SquareAsterisk, SquarePlus} from "lucide-react-native"; +import {SquareAsterisk, SquarePlus} from "lucide-react-native"; import ManualCardEntry from "./ManualCardEntry"; -import PhysicalCardScan from "./PhyscialCardScan"; const Stack = createNativeStackNavigator(); @@ -18,7 +15,6 @@ function CardAddModal() { - ) @@ -26,11 +22,6 @@ function CardAddModal() { function CardAddMethodSelector({navigation}) { const theme = useTheme(); - const [nfcSupported, setNfcSupported] = useState(null); - - useEffect(() => { - NfcManager.isSupported().then(setNfcSupported); - }, []); return ( <> @@ -45,19 +36,6 @@ function CardAddMethodSelector({navigation}) { - {nfcSupported !== null && ( - <> - : } - description={nfcSupported ? "Amusement IC or Transport cards supported" : "NFC is not supported on this device"} - onPress={() => navigation.navigate('Scan')}/> - - - - )} - } title="Enter Details Manually" @@ -106,8 +84,4 @@ function MethodCard(props: { ) } -// the nfc library doesn't offer the same support for felica cards on android compared to ios... -export default Platform.select({ - ios: CardAddModal, - default: ManualCardEntry -}); +export default CardAddModal; \ No newline at end of file diff --git a/amnet-native/app/card/PhyscialCardScan.tsx b/amnet-native/app/card/PhyscialCardScan.tsx deleted file mode 100644 index 3269c66..0000000 --- a/amnet-native/app/card/PhyscialCardScan.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import {StatusBar} from "expo-status-bar"; -import {useNavigation} from "@react-navigation/native"; -import {Card, Text, useTheme} from "react-native-paper"; -import {useCallback, useLayoutEffect, useState} from "react"; -import {Platform, TextInput, TouchableOpacity, View} from "react-native"; - -import RealmContext from "../../models/RealmContext"; -import {useModalStyles} from "../../utils/ModalStyles"; - -import {AMCard} from "../../models/AMCard"; -import {AMPhysicalCardData} from "../../models/AMPhysicalCardData"; -import {felicaCardReadAsync, icCard0008Number, ICType} from "../../lib/iccard"; - -const {useRealm} = RealmContext; - -export default function PhysicalCardScan() { - const realm = useRealm(); - const theme = useTheme(); - const navigation = useNavigation(); - const modalStyles = useModalStyles(); - - const [cardName, setCardName] = useState(''); - - const handleScan = useCallback(async () => { - const cardInfo = await felicaCardReadAsync(); - - if (!cardInfo) { - return; - } - - realm.write(() => { - // check if the card has already been registered (convert a manually entered card to a physically scanned card) - const accessCode = cardInfo.type === ICType.Amusement ? cardInfo.accessCode : icCard0008Number(cardInfo.idm); - const cardEntity = realm.objects(AMCard.schema.name).find(x => x.code === accessCode) ?? realm.create(AMCard.schema.name, AMCard.generate(accessCode, cardName)); - - // set physical card props - cardEntity.name = cardName; - cardEntity.added = new Date(); - cardEntity.physicalCardData ??= realm.objectForPrimaryKey(AMPhysicalCardData.schema.name, cardInfo.idm) ?? realm.create(AMPhysicalCardData.schema.name, { - cardIDm: cardInfo.idm, - cardType: cardInfo.type, - cardVendor: cardInfo.type === ICType.Amusement ? cardInfo.vendor : null - }); - }); - - // dismiss the parent screen (this screen can only be accessed from modal -> stack) - navigation.getParent()?.goBack(); - }, [realm, navigation, cardName]); - - useLayoutEffect(() => { - const canSave = cardName.length; - const themeOptions = canSave ? modalStyles.headerText : {color: theme.colors.outline}; - - navigation.setOptions({ - headerRight: () => handleScan()}> - Scan - - }) - }, [navigation, modalStyles, cardName]); - - return (<> - {Platform.OS == 'ios' && } - - - - - - Card Name - - - - - - - - Ensure you have either an Amusement IC or Transport card ready to scan. - After the scan has completed, the card will be saved to your device. - - - ) -} \ No newline at end of file diff --git a/amnet-native/lib/iccard-crypto.ts b/amnet-native/lib/iccard-crypto.ts deleted file mode 100644 index d474aec..0000000 --- a/amnet-native/lib/iccard-crypto.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {Buffer} from "buffer"; - -const s_box_inv = [ - "243cba36e385a4d0934373b9706ec9f1100e9b2c97e70b636c2920fe86f3e1f5f69fb616047b8fabb1392a1beb5ca8ac3811125f893e7dcaec53db6d1ed281789646fff9541c287a4fd3c0dcc16af2bccb57fd4ae4f0b2c79540625241e2ad49a6b51f02c8da92e5b0c564764821de0f45584cdfa784cfd5154e27806b7ffc44714722dd300ca13be037a03523903274bf8dc2ead650bbd9c483b43168558b5df47218b7eff7982d010361cc0a8e1300e814af095175a52f1d0d658acd66073d05d84be99d997c91d1b819c32b426988c679173a4d5ba25afb253fbdf8edcee687a326a92ebe94087e67608c9c5e6fb39e06fa82aeee5977d71a9a34d456aa33", - "f9817c00b93037d590516ef0b206fbcd39145ff81bc74a82708c921deac34ec812e6b610d32f95842542a572e58f55ef86a253aeed262047de786828e445cc35bc3ebb8ec0be34afa60964017b44f5f3b13fa4b33280c94f6f40bf214c74e1115eeb3d712e9d6275d41648771367ad6b5d0787f7a89a59768b9b1ed8eeaae9992dc597f1a783fccadcbab84be88917050ba06523d1ce3603d7e0f291df0e9c2c0d66d673585cb40a4dc23bd2b7e2ac33db6027bd56432404028d7d7ff6b5f4fddd5bec79c4221fa18854e71998947e31a329e35acb6ab0cf6d936c7a08a93c1cd06350c68538c14149ab61522a9fd9181a57462b69dafaff0f15963a8afe9e0c", - "11a29ac394a051015bf7d130ee1f06d57767d3c16ee76a1aa48e9f6566d60c2dc6e469b7565a5760fc791ee11652f007d0cdca78c98bb38827f6e0c084cf2fa304008040a7fa759d4b89462228373413ff20b4da08266c8ff25d7b735c4c9071418a9ebb12e8556d2a254aafbd9519a531baadd9c5a66b83c4b6a9ccf9a1b17adcc7db2e7e7f8d4e620f0339fdb8d418c2ec6102531c3b7dbc1d59f58c98f16f9385f82c74d85e4d97ab87820a21425f0d5833443adde9fb5047c8d7819be51b1754862beff429320e7c2315ebe63c35e296683f0b43cede9cbe4f457276b510e3df92d2a84891b9acbf49b2996470eaf33e6314aeedaa24fe3dcbb009383605", - "c4f02849554af5fd75af2069c843866bc9a8c6544cdd025be89b597734d7c0515ff37ed4f19081ce198a7833cd978cd66a4b8fa480dc1a1714c5078766ad9d85b2f88d98df3e1f3f645840ac6c5c085d53baffd2a92c25abe432389af9cbc29167d0e322292d9248b40e9905a36f0a15efd5104fd8f600e6462b31578394ae03eb8e1324a11e5ed126d9a7ca366e7137eeb1fa7c7606b823be21bc9ec1da7a3b62277bb0e56318827f0feda02a9cbf11bde0880d3a795256f7b6d309161b70b342602f1ca29f72124547bbe10b018b1ddeec0c4ee9cfcc9574f4a593c7db4dfb3565fee739b5ea96c304414484fc6d30e2f268b7897d733db95a50a63c61aa2e", - "1a599cadc8e41154ed370f3ae65f3c4bb81589b1e8da697791568bdb0624cf18f8b087df8c35cb86539da4664a7a710a4838ffdc8320ce9832f6d7af705e738a14721ec129790708e94346d02fde2a4f3d2c50b375fc0b64ae317b61a530a093d2bec255ba6cf36268fdac3b95491f6bb485f7a603ec6e9a81090c6aee9e4ef0ab2d7ea1e1bbc9bc41f2fa2e1bdd2734d36004b501d640f9d50247d9d8e08f2bfbb7c5eb5716e5788df400cd8239c696bdbfe336450e261d63e7843eb69b22a3f58e236dc3997d9097102580d44ce274ccb25c33c7ea05123f51a8fef11c42cad176286f92a767a919a2445baaef587f21c08865b95d4d0d5ac413527c9f9417", - "c72b82615bd09684d34a70a19b59339fc02014532917c50bc97b97020d3a1e7c3f6b52e8753df6e40c8a4fc35f2665733123284874aaa73609b1e291045122fc08a605a4f1121c19eb4037c2a0411dd4dc07438f47afd12e98ab01baf06668acf9e769b6cb8d78871503d5dfa31a9d6aea2f944e9e42d7b83892d8bbdedd9abcb04c79f4583ee98381ffe355fd5db2ef9c6d549960da3becfa11d6c42aed4bae13bfb9068be01f7f5aad90390ff3bd466c2cf2f8fed9e6720e89be5ec6a8cff5577e8cb7e1887ac81b18db6f35d216326463a58ef7b47695867de5a95c8500c1934dfb30a2b5253410773c67712d4445560a50b3cd6280ce214924caee276ecc", - "a5b06db251551fbf3e8ddd1992ea1bbd326529a48967b1906800da0da65954f210142f45e03b23fad750ac93e643010a5c78709846a97bf395072ad374b33fa36082394e3448c01cf6c291644d3cf89d359a94e37af0f96eb912b8042d02281385728087db2bf14f26a2e1497e9ccca7b6a0d09b3677ad8b6b4a031d058a064bafe531b5d4c60c66ba83fd090fae71b76fdc41e8178e407f6230ffa88425fb16ce3744ab991eeb183a47f75f81cfed582c6cfe9e57539720ca791a5a88f5699fe7d90ebe42df56e44c22aa730b15c5eefcc7d6cbcd8ce27621e9d1ecc87dd88f617c2ebcdeb475d2c4633da15e5d6a0824c927bbef33865bd5385211f4c396c1", - "345adb2c5957122b30c2a092bfedbc45de279b96d3e6c5ebd8244ba421cca8d6ce3cbab109e0d732664a831d19ca89670f420771e9bb445e8517c4ae9c3f6b78e76d02a7fe33e3d90a7deaf118877b6203795223003e25b9dd16683b1f90a62ac629918a9def1e8476ee4f39fb111b0b93ad49b705e14dcbe838d055f2f70d8b65cdfad49ec9814e14c826b6f9aaf580f86a98ab58f3b88eb5974372a2da64c1405c13b0e5bd089f1c7e8f06be6e501a2f37a1fc102e705304206348e2fd6f7a7561b3353acf5b888251d247a5a9746c31947c9ac0ec150e28012dc3f0b4e48ddcac3d5f86c79522f4b2a35446d1778c0cffaf565d36697f994cd56073df41f6", - "04da79631ebdeae30b652501cda992ed187557308903096adcc798a450911fb10c778566ca121c67c2e01783599dd5228256a89fc66048b311fca328fb06dd5dba297a4fe8fee910cbf3937b6c6954e744a2841d8dcefffa1a879074a1f814aabcc0cf31b4d9bfd85e262dd0e43f19d68c2fab3958726edf3be63cb66288de40b28f9b7c9543d19eac9cd423e10e4b5333462013341b97f7f1c3614a6f5a217f702e554105c5d776e52715ec425c4d78358befd2eeb5beae023ad35fc424f0f9514eeb000ffdaf3ec19a528681807ef62bccb97d68f2ad99a7072c7338b06bb78e71a0f43da60d37db0a473616966d322a5be24594f5a54cc88a4964bb08c9b8" -]; - -function rotate_right(data: Uint8Array, nBytes: number, nBits: number) { - let prior = data[nBytes - 1]; - - for (let i = 0; i < nBytes; i++) { - let current = data[i]; - - data[i] = (current >> nBits) | ((prior & ((1 << nBits) - 1)) << (8 - nBits)); - prior = current; - } -} - -const S_BOX_INV = s_box_inv.map(x => new Uint8Array(Buffer.from(x, 'hex'))); -const N_TABLES = S_BOX_INV.length - 1; -const ITER_ADD = 5; - -export function performDecrypt(data: Uint8Array) { - const spad = data.map(x => S_BOX_INV[N_TABLES][x]); - - let count = (spad[15] >> 4) + 7; - let table = spad[15] + ITER_ADD * count; - - for (let z = 0; z < count; z++) { - table -= ITER_ADD; - rotate_right(spad, 15, 5); - - for (let i = 0; i < 15; i++) { - spad[i] = S_BOX_INV[table % N_TABLES][spad[i]]; - } - } - - return { - version: spad[0], - accessCodeBytes: spad.slice(6) // access code is a binary-coded decimal - }; -} diff --git a/amnet-native/lib/iccard.ts b/amnet-native/lib/iccard.ts index 9404b69..d56ea2e 100644 --- a/amnet-native/lib/iccard.ts +++ b/amnet-native/lib/iccard.ts @@ -1,8 +1,3 @@ -import {Buffer} from "buffer"; -import NfcManager, {NfcTech} from "react-native-nfc-manager"; - -import {performDecrypt} from "./iccard-crypto"; - export enum ICType { Amusement = 1, Transport = 2 @@ -41,92 +36,3 @@ export function icCard0008Number(idm: string): string { // 0008 is the numerical representation of the idm padded with zeros until it is 20 digits return BigInt(`0x${idm}`).toString().padStart(20, '0'); } - -/** - * Reads a FeliCa card, returning access code information. - */ -export async function felicaCardReadAsync(): Promise { - try { - await NfcManager.requestTechnology(NfcTech.FelicaIOS, { - isReaderModeEnabled: true, - invalidateAfterFirstRead: false, - alertMessage: "Hold an Amusement IC or Transport card to the top of your phone to scan it." - }); - - const tag = await NfcManager.getTag(); - const idm = tag['idm'] as string; - - switch (tag['systemCode'] as string) { - case "0003": - return { - type: ICType.Transport, - idm - }; - - case "88B4": - return await readAmusementICCardContents(idm); - - default: - return null; - } - } catch (ex) { - return null; - } finally { - await NfcManager.cancelTechnologyRequest(); - } -} - -async function readAmusementICCardContents(idm: string): Promise { - const idmBytes = Uint8Array.from(Buffer.from(idm, 'hex')); - const readCommand = [ - 0x06, // read - ...idmBytes, - 0x01, // service count (fixed) - 0x0B, 0x00, // service code (000b, le) - 0x02, // block count - 0x80, 0x00, // S_PAD0 block - 0x80, 0x82 // ID block - ]; - - let response = await NfcManager.sendFelicaCommandIOS(readCommand); - const offset = response.indexOf(0x07); // look for 0x07 as for some reason the response may not start at response[0] - - // response must have 44 bytes - if (offset === -1 || response.length - offset !== 44) { - return null; - } else if (offset !== 0) { - response = response.slice(offset); - } - - // response format: - // 0 - read response (0x07) - // 1-8 - IDm - // 9 - status flag 1 - // 10 - status flag 2 - // 11 - number of blocks (0x02) - // 12-27 - S_PAD0 block - // 28-43 - ID block (DFC is 35-36) - - if (response[9] !== 0x00 || response[11] !== 0x02) { - return null; - } - - const dfcValue = response[37] | (response[36] << 8); - - if (dfcValue in AmusementICVendor && dfcValue !== AmusementICVendor.Unknown) { - const accessCode = performDecrypt(new Uint8Array(response.slice(12, 28))); - return { - type: ICType.Amusement, - accessCode: [...accessCode.accessCodeBytes].map(x => x.toString(16).padStart(2, '0')).join(''), - vendor: dfcValue, - idm - }; - } - - return { - type: ICType.Amusement, - accessCode: icCard0008Number(idm), - vendor: AmusementICVendor.Unknown, - idm - }; -} diff --git a/amnet-native/package-lock.json b/amnet-native/package-lock.json index b41741f..7ae0bac 100644 --- a/amnet-native/package-lock.json +++ b/amnet-native/package-lock.json @@ -1,12 +1,12 @@ { "name": "amnet-native", - "version": "1.3.2", + "version": "1.3.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "amnet-native", - "version": "1.3.2", + "version": "1.3.9", "dependencies": { "@babel/runtime": "^7.28.4", "@expo-google-fonts/jetbrains-mono": "^0.4.1", @@ -32,7 +32,6 @@ "react-native-context-menu-view": "^1.21.0", "react-native-gesture-handler": "^2.30.0", "react-native-get-random-values": "^2.0.0", - "react-native-nfc-manager": "^3.17.2", "react-native-paper": "^5.14.5", "react-native-reanimated": "~4.2.1", "react-native-safe-area-context": "5.6.2", @@ -1626,7 +1625,6 @@ "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-54.0.4.tgz", "integrity": "sha512-g2yXGICdoOw5i3LkQSDxl2Q5AlQCrG7oniu0pCPPO+UxGb7He4AFqSvPSy8HpRUj55io17hT62FTjYRD+d6j3Q==", "license": "MIT", - "peer": true, "dependencies": { "@expo/config-types": "^54.0.10", "@expo/json-file": "~10.0.8", @@ -7344,20 +7342,6 @@ "react-native": "*" } }, - "node_modules/react-native-nfc-manager": { - "version": "3.17.2", - "resolved": "https://registry.npmjs.org/react-native-nfc-manager/-/react-native-nfc-manager-3.17.2.tgz", - "integrity": "sha512-0NryP/Iw2hzw4MVH5KCngoRerNUrnRok6VfLrlFcFZRKyTQ7KTgpsdDxCB6cR33qYNyEDrWGBayfAI+ym5gt8Q==", - "license": "MIT", - "peerDependencies": { - "@expo/config-plugins": "*" - }, - "peerDependenciesMeta": { - "@expo/config-plugins": { - "optional": true - } - } - }, "node_modules/react-native-paper": { "version": "5.14.5", "resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.14.5.tgz", @@ -8519,9 +8503,9 @@ } }, "node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", + "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -8810,9 +8794,9 @@ } }, "node_modules/undici": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", - "integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==", + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", "license": "MIT", "engines": { "node": ">=18.17" diff --git a/amnet-native/package.json b/amnet-native/package.json index bbfd3da..392d9cf 100644 --- a/amnet-native/package.json +++ b/amnet-native/package.json @@ -1,6 +1,6 @@ { "name": "amnet-native", - "version": "1.3.2", + "version": "1.3.9", "private": true, "scripts": { "android": "expo run:android", @@ -33,7 +33,6 @@ "react-native-context-menu-view": "^1.21.0", "react-native-gesture-handler": "^2.30.0", "react-native-get-random-values": "^2.0.0", - "react-native-nfc-manager": "^3.17.2", "react-native-paper": "^5.14.5", "react-native-reanimated": "~4.2.1", "react-native-safe-area-context": "5.6.2", diff --git a/amnet-shared/package-lock.json b/amnet-shared/package-lock.json index ebda06e..9221e3f 100644 --- a/amnet-shared/package-lock.json +++ b/amnet-shared/package-lock.json @@ -1253,9 +1253,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lru-cache": { diff --git a/readme.md b/readme.md index 1a2b6c5..571467b 100644 --- a/readme.md +++ b/readme.md @@ -1,15 +1,19 @@
# AMNet -[TestFlight](https://testflight.apple.com/join/kHbeJTsQ) • [Firebase](https://appdistribution.firebase.dev/i/6acc3151357deaa8) • [Website](http://card.ppc.moe) • [Server DLLs](https://gitea.tendokyu.moe/ppc/amnet/releases) +[Firebase](https://appdistribution.firebase.dev/i/6acc3151357deaa8) • [Website](http://card.ppc.moe) • [Server DLLs](https://gitea.tendokyu.moe/ppc/amnet/releases) -An Aime Card Switcher for segatools-based games. +An Amusement IC Card Switcher for segatools-based games.
## Overview -AMNet is designed to simulate the `DEVICE/aime.txt` file, allowing multiple users to log into a game using their phone. -This was originally intended for use with maimai and CHUNITHM, but should be compatible with any segatools-based game with Aime support. +AMNet is designed to acts as dynamic version of the `DEVICE/aime.txt` file, allowing multiple users to log into a game using their own mobile devices. +This was originally intended for use with maimai and CHUNITHM, but should be compatible with any segatools-based game with that uses Amusement IC for account management. + +> [!WARNING] +> TestFlight is no longer available and must be sideloaded using AltStore or similar tooling for the forseeable future. +> See issue #1 for more details. ## Setup 1. Download the latest `amnet.dll` from the releases page @@ -26,7 +30,7 @@ This was originally intended for use with maimai and CHUNITHM, but should be com ; serverName - the name of the server to show to users (defaults to the computer name if not set) ; serverAddress - the address to listen on, including the protocol (don't set/change unless you know what you're doing) ; enableKeyboardMode - whether to enable hold-enter-to-login mode. Reads the file set at aimePath for card number (or DEVICE/aime.txt if not set). - ; useAimeDBForPhysicalCards - Whether physical cards should use AiMeDB to retrieve access codes. If the game is using a hosted network, set this to 1 to load the same account data/profile as you would get on a physical cab. + ; useAimeDBForPhysicalCards - Whether codes from real cards should use AiMeDB to retrieve access codes. If the game is using a hosted network, set this to 1 to load the same account data/profile as you would get on a physical cab. gameId=SDEZ ;serverName=CHUNI-PENGUIN ;serverAddress=http://0.0.0.0:6070 @@ -36,18 +40,6 @@ This was originally intended for use with maimai and CHUNITHM, but should be com 4. Launch the game, and check the amdaemon console for server startup messages 5. Access the using one of the interfaces (see below), add a new server `http://:6070`, import your existing card (or create a new one) and start playing! -### Physical Cards -AMNet 1.3 adds support for scanning physical cards on iOS devices. If you own a physical Aime/Banapass/NESiCA/e-Amusement card, you can scan it using the app and use it in-game. -By changing the `useAimeDBForPhysicalCards` in segatools.ini, you can choose how the card is read on a per-game basis: - -- `0` will send the 20-digit access code on the back of the card to the game and load the save data directly (default option). -- `1` - Send the card's IDm to the game and use AiMeDB to retrieve the 20-digit code assigned to the card. - -Mode `1` makes the app function in the same way to a real card reader and should be used if you want the same access code to show when scanning the card on a cab. -You should also use this option if you're connecting to a hosted server (AquaNet/rinnet/etc). - -Additionally, if `useAimeDBForPhysicalCards` is set to `1` and the game is failing to read the card, make sure an up-to-date version of segatools is being used. - ## Interfaces AMNet can be used on most platforms, see below for options available for each platform: @@ -60,8 +52,8 @@ AMNet can be used on most platforms, see below for options available for each pl iOS - TestFlight - Aime/Banapass/NESiCA/e-Amusement IC cards supported + .ipa download + *TestFlight is no longer available, sideload using AltStore or similar tooling Android