WiiSOAP/ecs.go
2022-10-18 13:33:15 -04:00

209 lines
6.0 KiB
Go

// 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 (
"errors"
"fmt"
"github.com/wii-tools/wadlib"
"log"
"strings"
)
const (
QueryOwnedTitles = `SELECT owned_titles.title_id, tickets.version
FROM owned_titles, tickets
WHERE owned_titles.title_id = tickets.title_id
AND owned_titles.account_id = $1`
QueryTicketStatement = `SELECT ticket, version FROM tickets WHERE title_id = $1`
AssociateTicketStatement = `INSERT INTO owned_titles (account_id, title_id, version)
VALUES ($1, $2, $3)`
// SharedBalanceAmount describes the maximum signed 32-bit integer value.
// It is not an actual tracked points value, but exists to permit reuse.
SharedBalanceAmount = 2147483647
)
func getBalance() Balance {
return Balance{
Amount: SharedBalanceAmount,
Currency: "POINTS",
}
}
func checkDeviceStatus(e *Envelope) {
e.AddCustomType(getBalance())
e.AddKVNode("ForceSyncTime", "0")
e.AddKVNode("ExtTicketTime", e.Timestamp())
e.AddKVNode("SyncTime", e.Timestamp())
}
func notifyETicketsSynced(e *Envelope) {
// TODO: Implement handling of synchronization timing
}
func listETickets(e *Envelope) {
accountId, err := e.AccountId()
if err != nil {
e.Error(2, "missing account ID", err)
return
}
rows, err := pool.Query(ctx, QueryOwnedTitles, accountId)
if err != nil {
log.Printf("error executing statement: %v\n", err)
e.Error(2, "database error", errors.New("failed to execute db operation"))
return
}
// Add all available titles for this account.
defer rows.Close()
for rows.Next() {
var titleId string
var version int
err = rows.Scan(&titleId, &version)
if err != nil {
log.Printf("error executing statement: %v\n", err)
e.Error(2, "database error", errors.New("failed to execute db operation"))
return
}
e.AddCustomType(Tickets{
TitleId: titleId,
Version: version,
// We do not support migration, ticket IDs, or revocation.
TicketId: "0",
RevokeDate: 0,
MigrateCount: 0,
MigrateLimit: 0,
})
}
e.AddKVNode("ForceSyncTime", "0")
e.AddKVNode("ExtTicketTime", e.Timestamp())
e.AddKVNode("SyncTime", e.Timestamp())
}
func getETickets(e *Envelope) {
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
//}
// 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
}
// We store title IDs in lowercase.
titleId = strings.ToLower(titleId)
// Query the ticket and current version for this title.
var ticket []byte
var version int
row := pool.QueryRow(ctx, QueryTicketStatement, titleId)
err = row.Scan(&ticket, &version)
if err != nil {
log.Printf("unexpected error purchasing: %v", err)
// TODO(spotlightishere): Can we more elegantly return an error when a title may not exist here?
e.Error(2, "error purchasing", nil)
return
}
// Associate the given title ID with the user.
//_, err = pool.Exec(ctx, AssociateTicketStatement, accountId, titleId, version)
//if err != nil {
// log.Printf("unexpected error purchasing: %v", err)
// e.Error(2, "error purchasing", nil)
//}
// The returned ticket is expected to have two other certificates associated.
ticketString := b64(append(ticket, wadlib.CertChainTemplate...))
e.AddCustomType(getBalance())
e.AddCustomType(Transactions{
TransactionId: "00000000",
Date: e.Timestamp(),
Type: "PURCHGAME",
TotalPaid: 0,
Currency: "POINTS",
ItemId: 0,
})
e.AddKVNode("SyncTime", e.Timestamp())
e.AddKVNode("ETickets", ticketString)
// 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) {
e.AddCustomType([]Transactions{
{
TransactionId: "00000000",
Date: e.Timestamp(),
Type: string(SERVICE),
TotalPaid: 0,
Currency: "POINTS",
ItemId: 0,
TitleId: "000100014843494A",
ItemPricing: []Limits{
LimitStruct(DR),
},
ReferenceId: "01234567890123456789012345678912",
ReferenceValue: 1,
},
})
e.AddKVNode("ListResultTotalSize", "1")
}
// 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"))
}