// Copyright (C) 2018-2020 CornierKhan1 // // WiiSOAP is SOAP Server Software, designed specifically to handle Wii Shop Channel SOAP. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see http://www.gnu.org/licenses/. package main import ( "bytes" "crypto/md5" "encoding/binary" "encoding/hex" "fmt" "html" "log" "math" "math/rand" "os" "strconv" "strings" "time" v1Ticket "github.com/OpenShopChannel/V1TicketGenerator" "github.com/antchfx/xmlquery" "github.com/wii-tools/wadlib" ) const ( QueryOwnedTitles = `SELECT owned_titles.title_id FROM owned_titles WHERE owned_titles.account_id = $1` QueryOwnedServiceTitles = `SELECT service_titles.reference_id, owned_titles.date_purchased, service_titles.item_id FROM service_titles, owned_titles WHERE service_titles.item_id = owned_titles.item_id AND service_titles.title_id = $1 AND owned_titles.account_id = $2` AssociateTicketStatement = `INSERT INTO owned_titles (account_id, title_id, version, item_id, date_purchased) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (accound_id) DO NOTHING` AssociatePointsStatement = `INSERT INTO public.userbase (device_id, device_token, device_token_hashed, account_id, region, serial_number, points) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (device_id) DO UPDATE SET points = $7` QueryPointsStatement = `SELECT points from public.userbase WHERE device_id = $1` AssociateGiftedTitleStatement = `INSERT INTO public.gifted_titles (title_id, trans_id, friend_code) VALUES ($1, $2, $3)` QueryGiftedTitleStatement = `SELECT trans_id FROM public.gifted_titles` RemoveGiftedTitleStatement = `DELETE FROM public.gifted_titles WHERE title_id = $1 AND trans_id = $2` // SharedBalanceAmount describes the maximum signed 32-bit integer value. // It is not an actual tracked points value, but exists to permit reuse. SharedBalanceAmount = 1000 // WiinoMaApplicationID is the title ID for the Japanese channel Wii no Ma. WiinoMaApplicationID = "000100014843494A" // WiinoMaServiceTitleID is the service ID used by Wii no Ma's theatre. WiinoMaServiceTitleID = "000101006843494A" ) // contentAesKey is the AES key that is used to encrypt title contents. var contentAesKey = [16]byte{0x72, 0x95, 0xDB, 0xC0, 0x47, 0x3C, 0x90, 0x0B, 0xB5, 0x94, 0x19, 0x9C, 0xB5, 0xBC, 0xD3, 0xDC} func getBalance(e *Envelope) Balance { var points string err := pool.QueryRow(ctx, QueryPointsStatement, e.DeviceId()).Scan(&points) if err != nil { e.Error(104, "Could not retrieve points balance.", err) } log.Printf("Points balance for device %s: %s", e.DeviceId(), points) if points == "" { points = "0" } pointsInt, err := strconv.Atoi(points) if err != nil { e.Error(114, "Could not convert points balance to integer.", err) } log.Printf("Points balance for device %s: %d", e.DeviceId(), pointsInt) return Balance{ Amount: pointsInt, Currency: "POINTS", } } func checkDeviceStatus(e *Envelope) { e.AddCustomType(getBalance(e)) e.AddKVNode("ForceSyncTime", "0") e.AddKVNode("ExtTicketTime", "0") e.AddKVNode("SyncTime", e.Timestamp()) } func notifyETicketsSynced(e *Envelope) { // TODO: Implement handling of synchronization timing } func listETickets(e *Envelope) { /* var titleIds [5]string var ticketIds [5]string titleIds[0] = "0001000144574641" ticketIds[0] = "0001000000000000" titleIds[1] = "000100014F484243" ticketIds[1] = "0023DB4424D033EC" titleIds[2] = "0001000452464E45" ticketIds[2] = "00019E9781E7E89F" titleIds[3] = "0001000452465045" ticketIds[3] = "0001F72A2763BFA2" titleIds[4] = "00010004524D4345" ticketIds[4] = "00019B1950DF85DF" for i := 0; i < len(titleIds); i++ { e.AddCustomType(Tickets{ TitleId: titleIds[i], Version: 1, // We do not support migration, ticket IDs, or revocation. TicketId: ticketIds[i], RevokeDate: 0, MigrateCount: 0, MigrateLimit: 0, }) } /*e.AddCustomType(Tickets{ TitleId: titleIds[2], Version: 1, // We do not support migration, ticket IDs, or revocation. TicketId: "0", RevokeDate: 0, MigrateCount: 0, MigrateLimit: 0, })*/ e.AddKVNode("ForceSyncTime", "0") e.AddKVNode("ExtTicketTime", "0") e.AddKVNode("SyncTime", e.Timestamp()) } func getETickets(e *Envelope) { /* var etickets [8]string etickets[0] = "AAEAAVtvJd7vSdT1RY1l6q5kaCHrdVis2lLa0/25pbbHaqr560eOnp/VpWjhGfD1ElTDJN3vRgb+ijzM9NrnBQvW/oFXAfsAyhw/UuO3Av7rB6cdd4e2wmpraxIavl9XL8nVrsKVbtI8vH35HUzTDO46ES1M7+cYtCIAWToSJE+UtWK0PNPAlhJZ2poKT96kiq+9bGT1u+Tls7pVrgv8ZnT4dDKUIZkBUWntfCU84WVpvl953lC/8Xff/B29umu9jjKpfvmx436Oi9TpZE94zRdR2DLs0kxtyreV/uIjBYLv2eZcjzskXNoOTEe+3++V5yvNW201bgUwAhiTlIVx+AQwgScAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSb290LUNBMDAwMDAwMDEtWFMwMDAwMDAwMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALTS+bbifI4ioLdMFp2KjnlkPV+Gq0jq3IjvdPz6AEIHZUFmW9vJW+S/PyWqqdndg67xqOOy5EOHteuwAAAAXl16v6DlPOAaZ7b6wUSvqwAAAY7miaw1FQJ7CPUAAQAASEFaQf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" etickets[1] = "AAEAATuy8VhF9O0027viWCvKomWmnnJVjhiuYsJ6ycMJzwRLn/o5IEY5v7MZiNY2Sn5+ixDfiean3Wny1Kq9DIMVwoERgCUkY4D051d4YPuPnraERmNU5oMU9DvZosCEdOWlihKYdUGyyCbk2r4uy9Sai+cv+ozru+FAB2LLsgG/t8U5ekKpbYHBB/Z1IfSJiqt7gg5Fn1o+NIeaMNZBYZHQU7tGjzYnkCGJQj00LusAnz76gtOlfYms9mpHr0iWWlDWDYeYosJ1XTf8Swuu0zUf5po1l0li767/sZ/N8oYRSMok2CkXSwyQHlrRJqlmK4IOUIhRrUfsIlwkvp6WSxeBnl8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSb290LUNBMDAwMDAwMDEtWFMwMDAwMDAwMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALTS+bbifI4ioLdMFp2KjnlkPV+Gq0jq3IjvdPz6AEIHZUFmW9vJW+S/PyWqqdndg67xqOOy5EOHteuwAAAACtSjNHpZMSx0mtlhmYd1LgAAAdSMLJjFDAJ7CPUAAQABSENSRf//AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" //----- //WiiSOAP eticket //etickets[2] = "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" //vWii eticket etickets[2] = "AAEAASNTxfAL/ZyPDwPVv2IUZsPS/NbZxdcoiHUt5pR33+IWAJEmkgp3SudX/BhuCKTdzGC9gdBIXWNVdzlx5sn0RtmOSsZkazePp2tU+BRi3EpJ8Oiz2Sh03BrZmOayRJybVsc/OfhtbAJo0XOXVoHWXjx/UfBpcPIf4X7Sm4pQUdD0/xQAo0OoCnWVBf1bNYy1VRBN5srdnLu0qSaVX7zj6MqrjXs1JRxYokQ2l1u0/2/Tj1u89Kw2dzgm8vb77kdLJ58co+IJYTii3hsCySItp0t2yzWnWVfRsJFNjsXTDxPwo6Uw+5YfoBrR5+EiuT0MgD5CJIqlJraeI8maqiL7ySMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSb290LUNBMDAwMDAwMDEtWFMwMDAwMDAwMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALTS+bbifI4ioLdMFp2KjnlkPV+Gq0jq3IjvdPz6AEIHZUFmW9vJW+S/PyWqqdndg67xqOOy5EOHteuwAAAAAXc90P6nEAXs/JOIzaCuVwAAAepYrULdZQJ7CPUAAQABSEFURf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" //----- etickets[3] = "AAEAAafzenrwRhEO0HsLSaejzYM+Lm8QW8ahw1PR+pZFEY4YFNygeyDkDtEWf9/aq3y1YG+cEPy1Y5Mrn1fhomhDPKQpoDr8ou3ZiZkiYLBzAp1EfxUIpeLSVwY5AsQ55U1+cihFLBRxC3l3eYxCWqIsiAZm1kj/79cRYHoDGIhRvbGfHjgEFPLF73IeIqZn5kkkukuRjX4PXVe6lungGZuTnvgsb/TzrCVRZ8hjFmCINUiOHxtG4CvybJqxnXekl8hVRmIG3eDmiWuHfskN+jHIe4YjVQotZLIB7FcZr1Tk35XJN08re99cxJfEGC/SRHd/VMwn0/WFJDQh6KWjy4fX1K8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSb290LUNBMDAwMDAwMDEtWFMwMDAwMDAwMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALTS+bbifI4ioLdMFp2KjnlkPV+Gq0jq3IjvdPz6AEIHZUFmW9vJW+S/PyWqqdndg67xqOOy5EOHteuwAAAAhz/e0Ych6ONikIIpXPQq1gAAAapyxP88DQJ7CPUAAQABSEFKRf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" etickets[4] = "AAEAAUth7dnIIm2kr6Bw3hZkkBCycZV+73NNOs95tTHFnYyO+GJ/zVOWF1s0Rne2PugqcPHWe9Skn6Z1gGmGdXMgNK5q0+QqWfoG/LkD4F8WvYO06ihcsXfDn9GTDDAcDfFTMWssApcpQeER18S/zG45tug+0ZnsayhmyBbyK6zJnZFm6Mxp9PsuuN79TRpK8pKpRPLXVjzKFUaY2Y3g+Tc+RHJ/ptDb+ONgP6iZ2kd9k81J1kugRn6DqisbC8ZKuEaHqeCQ3hLc9xKc2EfpVWW62grEbkWSZOM7ellar5BM0Hd3Ne3Xcsp8b1v4JFMVdRahrL94qVlFX7GugQubHK9tI4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSb290LUNBMDAwMDAwMDEtWFMwMDAwMDAwMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALTS+bbifI4ioLdMFp2KjnlkPV+Gq0jq3IjvdPz6AEIHZUFmW9vJW+S/PyWqqdndg67xqOOy5EOHteuwAAAAk+b/rdrXVvwTB4zE+vuiXgAAAUaiyG6pjQJ7CPUAAQABSEFQRf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" etickets[5] = "AAEAAZzaP2lz+bZIy6fSW/ncG+8BnyYhX6+lyEwGDBMlyTInPeNxvEohK4jjhZzYK6uvV4PM5MPLu0e3rfcEqzYTVKVJI9sn06O+JuTFzEIzyV5OTRRNqAlwzG+6KFSwMxwH5qOfwRCoSYsy6vw+F0QAbwFcdvuN0BpRVhDZKu390GBCM6sKTFE881oX+q2RfhKnA+ZFsSYlklpOAHwdlzRrlgVgnfkHTGh0JgtgcNtvWsPgfEZqkj5lrrvINWwr+yWZKrIhcD1xRIWX2w8zKBCExyqdmIfMKPDR0+RLzi4EAMWcNEA3pDUoneKcxE/tlZ3m5A0heRSCfTlbeMcyra0fR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSb290LUNBMDAwMDAwMDEtWFMwMDAwMDAwMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALTS+bbifI4ioLdMFp2KjnlkPV+Gq0jq3IjvdPz6AEIHZUFmW9vJW+S/PyWqqdndg67xqOOy5EOHteuwAAAARfNQD9UtTUOVSCUyDkupPAAAASdjIxILugJ7CPUAAQABSENTRf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" etickets[6] = "AAEAAX8fEr0hDFGPJalcFmdQztiwR3Fb8tvPEAfeA5kO2Nbynyw2bhL4MAV54sGsxHfBlcm5kTNnH+V+aBum2I+U/Y9sxXQhqvlNJ9qjBNCAJBR8DIz8//biXPwbh0hQOp4FaH9yYfGVNw4m7qrKvFiSVvrCLS4SZJx5Ka8QjiS2smDtj++b7F78C2k6UK7guCRgnVoUTg4po/4+FOSdfkKEdOIKNXKfnocJcUsdmOBO4NLK73/lDdz3XPjWTPtg2/IQ2UGxp5oH1Z+W57jcWFfYgIDcXk4E1xtBd4s33om4RMnL4jvVUtxA2xx/ixQ7H2j3Inqk71lTqEZDHFckwoUY9OUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSb290LUNBMDAwMDAwMDEtWFMwMDAwMDAwMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALTS+bbifI4ioLdMFp2KjnlkPV+Gq0jq3IjvdPz6AEIHZUFmW9vJW+S/PyWqqdndg67xqOOy5EOHteuwAAAAWnOfqtsyBhg0ReE0M9dVxwAAAV8mkF0mKAJ7CPUAAQABSEFXRQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" etickets[7] = "AAEAAR1dFX4o1WcYlRaHWYwiNPqKuGB6xHbZ46f8w9LBZXRkX/5P9ORxHKwjeWI0sAM3m0bCn4gR1et4821FrDw1gidnMcRqolZqvvtDhtRj3whdYbDgIii1lmpZPOOoWQnTKaKJp6/FUm+eytBB/wr9azJ69tyP3fp0Brg1m8dgRajg5gs2rTKOEpOeuCGMaQBB/a05TRf9zyfvOhL8kHQher8lB2AdkkL88AD2PqIDV4HeE82dk7hi/NI1HZVJejf5aOcswc3FhDNHFSBMKKBPGpVD7RHCjJVEoPXa7LhdZZ9nkpmgQaa1F8s06N5q/yD1rwtWnTyHca7U1z9BoTghn5EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSb290LUNBMDAwMDAwMDEtWFMwMDAwMDAwMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALTS+bbifI4ioLdMFp2KjnlkPV+Gq0jq3IjvdPz6AEIHZUFmW9vJW+S/PyWqqdndg67xqOOy5EOHteuwAAAAT2D7XouKddLkZlKJXqcuegAAtzdsE3eQLQJ7CPUAAQABSEFERf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" //etickets[8] = "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" var certs [2]string // vWii certs certs[0] = "AAEAALOtsyJrPD3/G0tAdxb/T3rXZIbIlaxWLSHxBgHU9mQoGRwHdo/fGuLOeyfJD7wK0DEleOwHebZX1DckE6f4bwwUwO9uCUHtKwXsOVc2B4kASoeNLp34x6Wp+MqzEbEYeVe7+JjiolQCz1Q5zyu/oOH4XAZug5rglMpH4BVY9W5vNOkqotw4k343zYxcTf0vEU/oaMmo2f7YbgwhdaK9fom5x7UT9Bp5YUQ5EO/51/5XIhjVbft/SXqky5DU8a6xduRoXaeUQGCYLwRIQB/Pxrrr2hYwtHO0FSM1CAcKn0+JeOYs7F6SRqWovaCFeGh1DDoRL6+V6DjImQ6HsWLNENqzMZZl74ibVBuzNrtnU5+vwq4tCi51wCN06k6sjZlQf1m5U3cwXyY1xgipkJOsj8beI7l66nC0xM9msw5YMg7FtnIESM47sRxTH8twKHy1wnxnT7v9jH/JQiCkcyMdWH5aGhqC43V5obuCbs4Bccl1Y0dLHUbmebKCN2IRzccAL0aHwjxtwNW1eG7h8nP/AZJQD/THUGrucrb0PfYI/qWDofmGD4evUkRUu0fDBgyU6Zv31jKnyKtLT/U1IR/BgEe7evpaK9e4hK2OVk9bif83lzfx9QE7H57EGG+SKtXEs8DVhwucBK8atfO8bQrxfUcI5EPpc/e3cHdUuvPs0qxJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUm9vdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFDQTAwMDAwMDAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAW/p9XLJ5yeLu4SHG6vRP9jn4jweLS3ftn5VgsDWCgbUOVatyERWhd3A8ejD+OunvHGC8HZdGdrI6aMwEsZhSW8lo8R3i21Dk2efwceVi2uIJIjPp02P2HdfBn/OkqR6PZVPUcd17hLnxuM5zNfD1VAVjoeq4OWPgm+kBAR+ZVGNhKHAg6cwNq0h/FA1mJqGDbScRHyBo3kdyFJFRz2nGG6YO+dlJoPcfVJny05rSjHAFNIKTxDH/vTP2vKYNxxleorzFbSALr20G0JxB243pxyAVTKSDK2nAjGnNOwc6AGNgL0YtM4BhpepskVzVYjV5w+tkzkTvWG0UuqqINAGbPuvu03kAAQABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" certs[1] = "AAEAAX2dXrpSgdynBl0vCGjbisc6zn6pkfGWn+HQ8sEfrsDD8BrctEat5coDtiUhlGLG4UENueY/3pjRryY7TLKHhCeCcu8nE0uHwljWe2Lytb+ctrqMiRkuxQaJrHQkoCIJQAPumKS9LwE7WT/lZmzV61rXpJMQ8077tD1Gy/G1I8+C9o61bbkEp8KoK+EdeNObog2Q0wdC2156we/yIVEJYs+pFKiA3PQXupmTCu4IsLDlGj6fr83C1+PLoS86wAeQ3kR6w8U4qGeSOAeL1MSyRawpFohtKg5ZTu1cyDVpi01iON8Fck3M9oGAinB0Blkwv/hRQTfoFfq6oXK44GlsYeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSb290LUNBMDAwMDAwMDEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVhTMDAwMDAwMDMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADxuJ/RrQepN4p7EAx9xzm+nt23MgCJqyWx+HGvWqn0WJ7RgwIyjoEaH+/QCcgGNkP4VLnhO7thOnrPhxSFa6Rbque7xk6y912H6/Jn7Q+kQakzZl5XfVreq/tGLnYAypzpTcTLmDmSq3ovs6Oeor+cU+zQ3Ppri16yy6QP+kB1+PKy3pc4EYct9eKmw4sv3I5X3b1fRusn1hlS9q74YrfumsaCorGaqbVY++uziS+9UMn13EpunJv+RYA0qUIYLd63X+DRs98Ol+OZgIdwGMKyg/E1dXxaMPw/MISkmqrAHucGaU+OFEjaEjrMT/omqjj3778njzaXeXddt8Wtx4mR3PhDjQABAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" //WiiSOAP certs //certs[0] = "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" //certs[1] = "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" for i := 0; i < len(etickets); i++ { e.AddKVNode("ETickets", etickets[i]) } for i := 0; i < len(certs); i++ { e.AddKVNode("Certs", certs[i]) }*/ e.AddKVNode("ForceSyncTime", "0") e.AddKVNode("ExtTicketTime", e.Timestamp()) e.AddKVNode("SyncTime", e.Timestamp()) } func purchaseTitle(e *Envelope) { accountId, err := e.AccountId() if err != nil { e.Error(2, "missing account ID", err) return } tempItemId, err := e.getKey("ItemId") if err != nil { e.Error(2, "missing item ID", err) return } // Our struct takes an integer rather than a string. itemId, _ := strconv.Atoi(tempItemId) // Determine the title ID we're going to purchase. titleId, err := e.getKey("TitleId") if err != nil { e.Error(2, "missing account ID", err) return } ticket := new(bytes.Buffer) var ticketStruct wadlib.Ticket err = binary.Read(bytes.NewReader(wadlib.TicketTemplate), binary.BigEndian, &ticketStruct) if err != nil { // Should never happen but report e.Error(2, "error reading ticket template", err) return } // We will now formulate the ticket for this title. intTitleId, err := strconv.ParseUint(titleId, 16, 64) if err != nil { e.Error(2, "invalid title id", err) return } ticketStruct.TitleID = intTitleId // Title key is encrypted with the common key and current title ID ticketStruct.UpdateTitleKey(contentAesKey) tikFile, err := os.ReadFile("./tickets/" + strings.ToLower(strings.Replace(titleId, "00010001", "", 1)) + ".tik") fmt.Println("ticket name: " + strings.ToLower(strings.Replace(titleId, "00010001", "", 1)) + ".tik") if err != nil { tikFile, err = os.ReadFile("/media/sdc1/hydrobleach/Local/ccs.cdn.shop.wii.com/ccs/download/" + titleId + "/cetk") fmt.Println("ticket name: " + titleId + ".tik") } /*wad, err := wadlib.LoadWADFromFile("../WiiLikeToParty/wad/ticket/" + strings.ToLower(titleId) + "_bogus.wad") if err != nil { e.Error(2, "couldn't read wad", err) return } wadTicket, err := wad.GetTicket() if err != nil { e.Error(2, "couldn't read ticket", err) return }*/ /*wad, err := wadlib.LoadWADFromFile("./smash bros.wad") if err != nil { e.Error(2, "couldn't read wad", err) return } wad.ChangeTitleKey(contentAesKey) tik, err := wad.GetTicket() if err != nil { e.Error(2, "couldn't read ticket", err) return } os.WriteFile("content/cetk", tik, 0777) wadBytes, err := wad.GetWAD(wadlib.WADTypeCommon) if err != nil { e.Error(2, "couldn't get wad", err) return } wadContent1, err := wad.GetContent(0) os.WriteFile("content/00000000", wadContent1, 0777) wadContent2, err := wad.GetContent(1) os.WriteFile("content/00000001", wadContent2, 0777) wadContent3, err := wad.GetContent(2) os.WriteFile("content/00000002", wadContent3, 0777) wadContent4, err := wad.GetContent(3) os.WriteFile("content/00000003", wadContent4, 0777) wadContent5, err := wad.GetContent(4) os.WriteFile("content/00000004", wadContent5, 0777) wadContent6, err := wad.GetContent(5) os.WriteFile("content/00000005", wadContent6, 0777) wadContent7, err := wad.GetContent(6) os.WriteFile("content/00000006", wadContent7, 0777) wadContent8, err := wad.GetContent(7) os.WriteFile("content/00000007", wadContent8, 0777) wadTmd, err := wad.GetTMD() os.WriteFile("content/tmd", wadTmd, 0777) os.WriteFile("smashnew.wad", wadBytes[:], 0777)*/ //version := 0 if titleId == WiinoMaServiceTitleID { // Wii no Ma needs the ticket to be in the v1 ticket format. // Update the ticket to reflect that. ticketStruct.FileVersion = 1 ticketStruct.AccessTitleMask = math.MaxUint32 ticketStruct.LicenseType = 5 err = binary.Write(ticket, binary.BigEndian, ticketStruct) if err != nil { e.Error(2, "failed to create ticket", err) return } refId, err := e.getKey("ReferenceId") if err != nil { e.Error(2, "missing reference ID", err) return } // Convert reference ID to bytes refIdBytes, err := hex.DecodeString(refId) log.Printf("refIdBytes err: %b", refIdBytes) if err != nil { log.Printf("unexpected error converting reference id to bytes: %v", err) e.Error(2, "error purchasing", nil) return } var referenceId [16]byte copy(referenceId[:], refIdBytes) subscriptions := []v1Ticket.V1SubscriptionRecord{ { ExpirationTime: uint32(time.Now().AddDate(0, 1, 0).Unix()), ReferenceID: referenceId, }, } // Query the database for other purchased items of the same title id. rows, err := pool.Query(ctx, QueryOwnedServiceTitles, titleId, accountId) if err != nil { log.Printf("unexpected error purchasing: %v", err) e.Error(2, "error purchasing", nil) return } defer rows.Close() for rows.Next() { var currentRefIdString string var purchasedTime time.Time err = rows.Scan(¤tRefIdString, &purchasedTime, nil) if err != nil { log.Printf("unexpected error purchasing: %v", err) e.Error(2, "error purchasing", nil) return } refIdBytes, err = hex.DecodeString(currentRefIdString) if err != nil { log.Printf("unexpected error converting reference id to bytes: %v", err) e.Error(2, "error purchasing", nil) return } var currentReferenceId [16]byte copy(currentReferenceId[:], refIdBytes) subscriptions = append(subscriptions, v1Ticket.V1SubscriptionRecord{ ExpirationTime: uint32(purchasedTime.AddDate(0, 0, 30).Unix()), ReferenceID: currentReferenceId, }) } newTicket, err := v1Ticket.CreateV1Ticket(ticket.Bytes(), subscriptions) if err != nil { log.Printf("unexpected error creating v1Ticket: %v", err) e.Error(2, "error creating ticket", nil) return } ticket = bytes.NewBuffer(newTicket) } else { // Validate that this title exists. app, err := GetOSCApp(titleId) if err != nil { e.Error(2, "an error has occurred retrieving app metadata", err) return } if app == nil { e.Error(2, "title does not exist", nil) return } err = binary.Write(ticket, binary.BigEndian, ticketStruct) if err != nil { e.Error(2, "failed to create ticket", err) return } } // Associate the given title ID with the user. //_, err = pool.Exec(ctx, AssociateTicketStatement, accountId, titleId, version, itemId, time.Now().UTC()) //if err != nil { // log.Printf("unexpected error purchasing: %v", err) // e.Error(2, "error purchasing", nil) //} amount, err := e.getKey("Amount") if err != nil { e.Error(2, "couldn't get amount", err) } amount = strings.ReplaceAll(amount, ".", "") amountInt, err := strconv.Atoi(amount) if err != nil { e.Error(2, "couldn't convert amount to integer", err) } // The returned ticket is expected to have two other certificates associated. //ticketString := b64(append(ticket.Bytes(), wadlib.CertChainTemplate...)) ticketString := b64(tikFile) //var pointsToRemove int e.AddCustomType(getBalance(e)) e.AddCustomType(Transactions{ TransactionId: "00000000", Date: e.Timestamp(), Type: "PURCHGAME", TotalPaid: amountInt, Currency: "POINTS", ItemId: itemId, ItemPricing: Prices{ ItemId: itemId, Price: Price{Amount: amountInt, Currency: "POINTS"}, Limits: LimitStruct(PR), LicenseKind: PERMANENT, }, }) var points string deviceToken, err := e.getKey("DeviceToken") if err != nil { e.Error(2, "missing device token", err) return } deviceToken = strings.Split(deviceToken, "-")[1] md5DeviceToken := fmt.Sprintf("%x", md5.Sum([]byte(deviceToken))) serialNo, err := e.getKey("SerialNo") if err != nil { e.Error(2, "missing serial", err) return } points = strconv.Itoa(getBalance(e).Amount - amountInt) _, err = pool.Exec(ctx, AssociatePointsStatement, e.DeviceId(), deviceToken, md5DeviceToken, accountId, e.region, serialNo, points) if err != nil { e.Error(103, "error calculating points", err) } e.AddKVNode("SyncTime", e.Timestamp()) //log.Println(ticketString) e.AddKVNode("ETickets", ticketString) //e.AddKVNode("ETickets", "AAEAAR1dFX4o1WcYlRaHWYwiNPqKuGB6xHbZ46f8w9LBZXRkX/5P9ORxHKwjeWI0sAM3m0bCn4gR1et4821FrDw1gidnMcRqolZqvvtDhtRj3whdYbDgIii1lmpZPOOoWQnTKaKJp6/FUm+eytBB/wr9azJ69tyP3fp0Brg1m8dgRajg5gs2rTKOEpOeuCGMaQBB/a05TRf9zyfvOhL8kHQher8lB2AdkkL88AD2PqIDV4HeE82dk7hi/NI1HZVJejf5aOcswc3FhDNHFSBMKKBPGpVD7RHCjJVEoPXa7LhdZZ9nkpmgQaa1F8s06N5q/yD1rwtWnTyHca7U1z9BoTghn5EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSb290LUNBMDAwMDAwMDEtWFMwMDAwMDAwMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALTS+bbifI4ioLdMFp2KjnlkPV+Gq0jq3IjvdPz6AEIHZUFmW9vJW+S/PyWqqdndg67xqOOy5EOHteuwAAAAT2D7XouKddLkZlKJXqcuegAAtzdsE3eQLQJ7CPUAAQABSEFERf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") // Two cert types must be present. e.AddKVNode("Certs", b64(wadlib.CertChainTemplate)) e.AddKVNode("Certs", b64(wadlib.CertChainTemplate)) e.AddKVNode("TitleId", titleId) } func listPurchaseHistory(e *Envelope) { accountId, err := e.AccountId() if err != nil { e.Error(2, "missing account ID", err) return } titleId, err := e.getKey("ApplicationId") if err != nil { e.Error(2, "missing application ID", err) return } var transactions []Transactions if titleId == WiinoMaApplicationID { // We will query the database differently for Wii no Ma. rows, err := pool.Query(ctx, QueryOwnedServiceTitles, WiinoMaServiceTitleID, accountId) if err != nil { log.Printf("unexpected error querying owned service titles: %v", err) e.Error(2, "error purchasing", nil) return } defer rows.Close() for rows.Next() { var refId string var purchasedTime time.Time var itemId int err = rows.Scan(&refId, &purchasedTime, &itemId) if err != nil { log.Printf("unexpected error purchasing: %v", err) e.Error(2, "error purchasing", nil) return } transaction := Transactions{ TransactionId: "00000000", // (Sketch) I don't know why but Wii no Ma won't acknowledge the entry if it isn't past a day from // purchase. Date: strconv.Itoa(int(purchasedTime.AddDate(0, 0, -1).UnixMilli())), Type: "PURCHGAME", TotalPaid: 0, Currency: "POINTS", ItemId: itemId, ItemPricing: Prices{ ItemId: itemId, Price: Price{ Amount: 0, Currency: "POINTS", }, Limits: LimitStruct(PR), LicenseKind: SERVICE, }, TitleId: WiinoMaServiceTitleID, ItemCode: itemId, ReferenceId: refId, } transactions = append(transactions, transaction) } } else { transactions = append(transactions, Transactions{ TransactionId: "00000000", // Is timestamp in milliseconds, placeholder one is Wed Oct 19 2022 18:02:46 Date: "1666202566218", Type: "PURCHGAME", TotalPaid: 0, Currency: "POINTS", ItemId: 0, ItemPricing: Prices{ ItemId: 0, Price: Price{ Amount: 0, Currency: "POINTS", }, Limits: LimitStruct(PR), LicenseKind: PERMANENT, }, TitleId: "000101006843494A", }) } e.AddCustomType(transactions) e.AddKVNode("ListResultTotalSize", strconv.Itoa(len(transactions))) } // genServiceUrl returns a URL with the given service against a configured URL. // Given a baseUrl of example.com and genServiceUrl("ias", "IdentityAuthenticationSOAP"), // it would return http://ias.example.com/ias/services/ias/IdentityAuthenticationSOAP. func genServiceUrl(service string, path string) string { return fmt.Sprintf("http://%s.%s/%s/services/%s", service, baseUrl, service, path) } func getECConfig(e *Envelope) { contentUrl := fmt.Sprintf("http://ccs.%s/ccs/download", baseUrl) e.AddKVNode("ContentPrefixURL", contentUrl) e.AddKVNode("UncachedContentPrefixURL", contentUrl) e.AddKVNode("SystemContentPrefixURL", contentUrl) e.AddKVNode("SystemUncachedContentPrefixURL", contentUrl) e.AddKVNode("EcsURL", genServiceUrl("ecs", "ECommerceSOAP")) e.AddKVNode("IasURL", genServiceUrl("ias", "IdentityAuthenticationSOAP")) e.AddKVNode("CasURL", genServiceUrl("cas", "CatalogingSOAP")) e.AddKVNode("NusURL", genServiceUrl("nus", "NetUpdateSOAP")) } func purchasePoints(e *Envelope) { deviceToken, err := e.getKey("DeviceToken") if err != nil { e.Error(2, "missing device token", err) return } deviceToken = strings.Split(deviceToken, "-")[1] md5DeviceToken := fmt.Sprintf("%x", md5.Sum([]byte(deviceToken))) accountId, err := e.getKey("AccountId") if err != nil { e.Error(2, "missing account ID", err) return } serialNo, err := e.getKey("SerialNo") if err != nil { e.Error(2, "missing serial", err) return } itemId, err := e.getKey("ItemId") if err != nil { e.Error(2, "missing item ID", err) return } amount, err := e.getKey("Amount") if err != nil { e.Error(2, "couldn't get amount", err) } currency, err := e.getKey("Currency") if err != nil { e.Error(2, "couldn't get currency", err) } var pointsToAdd int var points string if itemId == "100008" { pointsToAdd = 1000 } else if itemId == "100030" { pointsToAdd = 2000 } else if itemId == "100031" { pointsToAdd = 3000 } else if itemId == "100032" { pointsToAdd = 5000 } points = strconv.Itoa(getBalance(e).Amount + pointsToAdd) _, err = pool.Exec(ctx, AssociatePointsStatement, e.DeviceId(), deviceToken, md5DeviceToken, accountId, e.region, serialNo, points) if err != nil { e.Error(113, "error purchasing points", err) } itemIdStr, err := strconv.Atoi(itemId) if err != nil { e.Error(201, "couldn't convert item id to string", err) } rand.NewSource(time.Now().UnixNano()) // Generate a random 8-digit integer transactionId := 10000000 + rand.Intn(90000000) e.AddCustomType(PointsPurchaseInfo{ Transactions: PointsTransactions{ TransactionId: strconv.Itoa(transactionId), Date: e.Timestamp(), Type: "PURCHPOINTS", TotalPaid: amount, Currency: currency, ItemId: itemId, ItemPricing: GiftPrices{ ItemId: itemIdStr, Price: GiftPrice{ Amount: amount, Currency: currency, }, }, }, }) } func checkAccountBalance(e *Envelope) { e.AddCustomType(getBalance(e)) } func giftTitle(e *Envelope) { accountId, err := e.getKey("AccountId") if err != nil { e.Error(1, "missing mandatory key named AccountId", err) } titleId, err := e.getKey("TitleId") if err != nil { e.Error(1, "missing mandatory key named TitleId", err) } notes, err := e.getKey("Notes") if err != nil { e.Error(1, "missing mandatory key named Notes", err) } unescapedNotes := html.UnescapeString(notes) doc, err := xmlquery.Parse(strings.NewReader(unescapedNotes)) if err != nil { fmt.Println("Error:", err) return } senderFCNode := xmlquery.FindOne(doc, "//DeviceCode") var senderFC string if senderFCNode != nil { senderFC = senderFCNode.InnerText() } else { e.Error(124, "Cannot find sender friend code", nil) } amount, err := e.getKey("Amount") if err != nil { e.Error(2, "couldn't get amount", err) } amount = strings.ReplaceAll(amount, ".", "") log.Println("New amount: " + amount) amountInt, err := strconv.Atoi(amount) if err != nil { e.Error(2, "couldn't convert amount to integer", err) } rand.NewSource(time.Now().UnixNano()) // Generate a random 8-digit integer transactionId := 10000000 + rand.Intn(90000000) log.Println(transactionId) _, err = pool.Exec(ctx, AssociateGiftedTitleStatement, titleId, strconv.Itoa(transactionId+1), senderFC) if err != nil { e.Error(123, "error putting title in gifted titles table", err) } var points string deviceToken, err := e.getKey("DeviceToken") if err != nil { e.Error(2, "missing device token", err) return } deviceToken = strings.Split(deviceToken, "-")[1] md5DeviceToken := fmt.Sprintf("%x", md5.Sum([]byte(deviceToken))) serialNo, err := e.getKey("SerialNo") if err != nil { e.Error(2, "missing serial", err) return } points = strconv.Itoa(getBalance(e).Amount - amountInt) _, err = pool.Exec(ctx, AssociatePointsStatement, e.DeviceId(), deviceToken, md5DeviceToken, accountId, e.region, serialNo, points) if err != nil { e.Error(133, "error calculating points", err) } e.AddCustomType(getBalance(e)) e.AddCustomType(GiftTransactions{ TransactionId: strconv.Itoa(transactionId), Date: e.Timestamp(), Type: "PGIFTGAME", }) e.AddCustomType(GiftTransactions{ TransactionId: strconv.Itoa(transactionId + 1), Date: e.Timestamp(), Type: "RGIFTGAME", }) } func acceptGiftTitle(e *Envelope) { titleId, err := e.getKey("TitleId") if err != nil { e.Error(2, "missing mandatory key named TitleId", err) } accept, err := e.getKey("Accept") if err != nil { e.Error(2, "missing mandatory key named Accept", err) } transId, err := e.getKey("TransactionId") if err != nil { e.Error(2, "missing mandatory key named TransactionId", err) } if accept != "1" { e.Error(10, "not accepting", nil) } _, err = pool.Exec(ctx, RemoveGiftedTitleStatement, titleId, transId) if err != nil { e.Error(143, "error removing title from database", err) } //Probably need to add ETicket, but will do later. }